【JS30 挑戰】 Day1 - JavaScript Drum Kit

這個專案對 JavaScript 只要有 function 跟 DOM 基礎就沒問題。另外需要有一點 HTML/CSS 的概念才有辦法順利使用 querySelector

JavaScript30 是一個由 Wes Bos 提出的免費教學計畫。藉由每天完成一個小專案,練習基礎 JavaScript 。整個計畫為期30天。除了會提供教學影片外,也會先做好前期準備(例如: HTML/CSS 等相關建置),練習時只需專注於撰寫 JavaScript 部分的程式碼即可。

目標

  1. 當按下鍵盤按鍵時,撥放特定音效。
  2. 當按下鍵盤按鍵時,讓畫面上的按鍵圖示呈現變化,並在變化結束後恢復原狀。

練習內容

  • DOM querySelector
  • .addEventLinstener()
  • function
  • .forEach()

開始前的準備

雖然這部分已由教學計畫提供,但有些東西對我來說還是新知識,所以還是做個筆記,萬一以後自己想做類似功能也不用再另外找資料。

1. 幫網頁加上音效:
僅需要在 HTML 檔案中加入 <audio>標籤,並做好相關屬性設定。

2. 設定好鍵盤按鍵:
因為我們要在按下鍵盤按鍵時,讓網頁做出指定回應,所以要先建立相關標籤屬性,讓電腦辨識不同的按鍵,以便為按鍵設定不同回應。可以看到教學計畫提供的 HTML 檔案已經為聲音跟按鍵都設好 data-key 這個屬性(attribute)並給予一個數值。鍵盤上的每個按鍵在按下時會有一個對應的 keycode ,只要知道 keycode 就可以對指定按鍵進行動作設定。

如果不知道 keycode 可以到 keycode.info 查詢。或是隨便開啟一個網頁,打開開發人員工具,在 console 區寫下下列程式碼後,回到網頁內容範圍(滑鼠點一下)在按下鍵盤按鍵,console 就會印出 keycode 了。

1
2
3
window.addEventListener('keydown', function(e) {
console.log(e.keyCode)
})

3. 做好 CSS 設定:
專案目標是讓螢幕上的按鍵圖示在按下時產生變化,並在變化結束後恢復原狀。所以要設定好兩種 CSS 樣式:

  • 原本的按鍵樣式: 在 {} 裡加上 transition: all .07s; ,決定 要改變的屬性(property)變化要花費的時間
  • 變化後的按鍵樣式: 在 {} 裡加上 transform: scale(縮放倍數);

練習開始

1. 定好 Event Linstener 的範圍和要執行的動作。

1
2
3
4
window.addEventListener('keydown', playAudio)
// window 為 document 再往上一層的物件,指監聽整個瀏覽器分頁。
// 'keydown' 指鍵盤按鍵被按下時。
// playAudio 指執行名稱叫 playAudio 的fuction。

2. 設定 playAudio() 要做的事。(撥放音效及畫面改變)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function playAudio(e) {
// e 指 .addEventListener() 在 'keydown' 發生時自動產生的 event 物件。
const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`)
// 把 audio 變數指向 keyCode 相符的 <audio> 標籤。
const key = document.querySelector(`.key[data-key="${e.keyCode}"]`)
// 把 key 變數指向 keyCode 相符的 <div> 標籤。
if (!audio) return
// 如果找不到相符標籤,則中斷 function。
audio.currentTime = 0
// 如果找到相符標籤,按下按鍵時,音樂撥放進度回到第 0 秒。如果沒設定,在音訊完整前,按鍵不會重新撥放。
// 因為 function 要求按鍵時撥放音訊,還沒放完 = 已經/正在放
audio.play()
// 撥放 keyCode 指向的 <audio> 標籤。
key.classList.add('playing')
// 指定的 <div> 標籤 class 屬性加上 'playing'
}

3. 先來看看目前為止發生了什麼事?
指定的 <div> 標籤 class 屬性加上 ‘playing’ 後,當 CSS ‘.key’ 中的 transition 完成。

1
2
3
4
5
6
const keys = document.querySelectorAll(".key")
// 找出所有 class 含 ".key" 的 <div> 標籤,總共9個。
keys.forEach(key => key.addEventListener('transitionend', function(e) {
console.log(e)
}))
// 檢查找出的所有 <div> 標籤,在 'transitionend' 時,輸出 Event 內容。

TransitionEvent-image
可以看到,在我們按了一下按鍵”A”後,總共出現了6個 transitionend ,只有 propertyName 不同。那是因為我們的 CSS 設定。

1
2
3
4
5
6
7
8
9
10
11
12
13
.key {
transition: all .07s ease;
/* 所有 property 都要執行 transition */
}

.playing {
transform: scale(1.1);
/* propertyName: 'transform' */
border-color: #ffc600;
/* border 分上下左右,所以有4個 'transitionend' */
box-shadow: 0 0 1rem #ffc600;
/* propertyName: 'box-shadow' */
}

4. 畫面在變化結束後恢復原狀。

1
2
3
4
const keys = document.querySelectorAll(".key")
// 共找出9個 class 含 ".key" 的 <div> 標籤。
keys.forEach(key => key.addEventListener('transitionend', removeTransition))
// 'transitionend' 時,執行名為 removeTransition 的function。

這時,我總共找出9個 class 含 “.key” 的 <div> 標籤,但是想要刪除 class ‘.playing’ 的只有我們按的那一個。所以我們找出 這個 <div> 標籤所有 CSS 樣式裡最具代表性的 transform (不用寫一長串 property ,最具代表性的就好),來繼續執行。

1
2
3
4
5
6
7
function removeTransition(e) {
// 查找 TransitionEvent 裡動作(type)為 'transitionend' 的項目。
if (e.propertyName !== 'transform') return
// 如果 TransitionEvent 的 propertyName 不等於 'transform' ,則停止 function。
this.classList.remove('playing')
// 如果符合,則將 class 裡的 '.playing' 刪除。
}


到這裡就全部完成了!

文章內容如有錯誤,歡迎留言討論!


本 Blog 上的所有文章除特别聲明外,均採用 CC BY-SA 4.0 協議 ,轉載請註明出處!