Demo
前言
打開 YouTube 首頁時看到這個推薦影片 (Memory Card Game - JavaScript Tutorial),教導如何使用 Vanilla JavaScript 撰寫記憶卡牌遊戲。
- Demo: https://marina-ferreira.github.io/memory-game/
- Code: https://github.com/code-sketch/memory-game/
照著影片練習過一次後,我在第二次的撰寫加入了一些想法,例如計時功能、破關之後的恭喜訊息,還有重新開始遊戲等等。
簡介
記憶遊戲,點擊卡片進行配對。
使用工具或技術
- HTML
- data 屬性
- CSS
- 3D 屬性
- JavaScript
- ES6 語法
過程
詳細操作可以看影片,這裡只節錄影片中的主要步驟與一些用到的觀念技巧,主要為自己隨後加上的功能與做法。
影片中的建立步驟
- 建立資料夾與檔案
- 撰寫 HTML 並引入 CSS 和 JavaScript 檔案
- 重置 CSS
- 調整元素的樣式
- 抓取所有卡片到 JavaScript 並為它們註冊點擊事件,加上翻面效果
- 到 CSS 設定 3D 翻面效果
- 使用 JavaScript 儲存卡片以配對
- 用 data 屬性儲存卡片的花色,並且進行配對
- 重構程式碼
- 處理細節問題:配對失敗的卡片翻回來之前,鎖定畫面
- 處理細節問題:避免重複點擊卡片被判斷為配對成功
- 使用立即函式洗牌
影片中用到的觀念與技巧
- Position
- 使用
Position: absolute
時,會往上層查找有被定位的元素,以該元素為定位基準。
- 使用
- Flexbox
- 在外層元素加上
display: flex
,外層元素和內層元素會有這些預設屬性:- 外層元素 (flex-container)
- flex-direction: row
- flex-wrap: nowrap
- justify-content: flex-start
- align-items: stretch
- 內層元素 (flex-item)
- flex: 1
- 外層元素 (flex-container)
- 在外層元素加上
- calc
- 使用
calc()
時,裡面的運算符號前後都要留一格,不能緊跟著前後的數字。 - 例如:
width: calc(25% - 10px)
- 使用
- 3D Effect
perspective
在外層元素加上景深,transform-style
設定卡片位在 3D 空間backface-visibility
隱藏元素的背面transform: rotateY(180deg)
實現卡片翻面
- 使用
forEach()
和addEventListener()
為每張卡片監聽事件 - 使用
classList.add()
、classList.remove()
、classList.toggle()
去調整 HTML 元素的 class - 借助變數
hasFlipedCard
的值為true/false
的判斷讓卡片兩兩一組 - 當配對成功時,用
removeEventListener()
移除事件的監聽 - data 屬性
- 使用
data-*
屬性在 HTML 儲存卡片的花色 - 並在 JavaScript 用
dataset
取得花色
- 使用
- 程式碼重構
- 將程式碼以更小功能拆解
- 用三元運算式來簡化 if/else
- 用 return 來簡化 if/else
- 借助變數
lockBoard
鎖定畫面 - 使用
Math.random()
產生亂數,並用Math.floor()
取得最小整數。 - 使用 CSS flex 的 order 屬性為卡片排序。
增加計時功能
照著影片練習過一次之後,有了一些想法,覺得可以加上去。例如計時功能,並在破關時顯示恭喜訊息等等。
我的想法是,網頁載入後,一旦點擊卡片所在範圍 (.memory-game
),就開始計時。由於這個動作只需要在第一次點擊時執行,因此啟動之後先移除這個監聽事件,以免每次點擊卡片都觸發這個動作。
然後,用 new Date()
取得點擊當下的時間作為起始時間,每秒執行一次 countTime()
以計算時間並且更新到畫面。
1 | function startTimer() { |
接下來,countTime()
會每秒取得一次目前的時間,並與起始時間相減,這樣就能知道經過多少時間。再將時間以分和秒個別處理,最後呈現到 HTML 畫面。
1 | function countTime() { |
最後,當所有的卡片都配對成功,也就是畫面中沒有未翻開的卡片時,就停止計時,並顯示恭喜等訊息。
為此,先取得所有卡片的數量,紀錄在變數 unflipCardNumbers
裡,表示未翻開的卡片數量。只要畫面上有卡片配對成功,就執行 countUnflipCards
,把 unflipCardNumbers
減二。等到 unflipCardNumbers
等於 0
的時候,就執行 stopTime
把計時暫停。
1 | let unflipCardNumbers = cards.length |
顯示訊息與重新開始
當畫面上所有的牌都配對成功之後,除了停止計時,也會移除我加在訊息視窗的 none
,並且顯示恭喜訊息、花費的時間,還有重新開始遊戲的按鈕。
如果按下重新開始遊戲的按鈕,會將 none
加回到訊息視窗,以關閉訊息視窗,並且重新整理網頁來重新開始遊戲。
1 | function stopTime() { |
後記
這個影片讓我學到很多,例如 3D 效果的使用,透過 CSS flex 的 order 讓元素排序,還有一些 JavaScript 的撰寫思路。
最後我也將自己對於計時、顯示訊息的想法加到這個遊戲,並將卡片和背景顏色改成了漸層色。雖然總覺得破關後道恭喜的訊息視窗簡陋了點,不過目前還沒其他更好的想法,就先這樣吧。