0%

JS 30 - 01. Drum Kit 鍵盤音樂

有開頭總是好事,沒開頭就什麼事也不會發生。

這個練習是在畫面上顯示好幾個鍵盤上面的鍵,讓使用者在按下鍵盤,依鍵盤不同而發出不同的聲響,原本的練習是以英文字母為發音鍵,但自己愛好自然就把它們換成了數字鍵,並搭配各種不同動物的叫聲也頗有趣的。

如果依照「功能」和「畫面」來拆解這個練習,那麼也表示「功能的部分」需要用 DOM 和 JavaScript 來操控,而「畫面」部分則需要先用 HTML + CSS 來刻好,以 JS 來等待觸發事件,來播放音效與變換元件(鍵)的狀態。

功能: 按下鍵時,觸發相對應的聲音。離開鍵時聲音消失。
畫面: 按下實際鍵時,畫面上對應的鍵會有反應,搭配動畫效果。離開自下去的鍵時會恢復原狀。

  • 如何監聽到按到哪個鍵: -> 監聽 keydown 事件
  • 鍵在按下去的時候播放音效: -> 呼叫播放音效的函式 -> playSound()
  • 鍵在按下去的同時,播放動畫效果 CSS: -> key.classList.add('playing');
  • 鍵在按下去之後恢復原狀: -> 呼叫removeTransition(event) 執行event.target.classList.remove('playing');作切換。

HTML 的部分

  • 依序將需要的鍵顯示出來:<kbd>7</kbd>表示鍵入數字 7
  • 使用data-這個屬性來放入自定義的資料,這裡我們用data-key來定義。
1
2
3
4
<div data-key="55" class="key">
<kbd>7</kbd>
<span class="sound">Loup</span>
</div>

再以data-key將我們會使用到的audio音效關聯起來。

1
<audio data-key="55" src="sounds/loup.wav"></audio>
關於 data- 屬性
  • data-屬性可以讓我們自定義資料,它可以讓我們在任意的html標籤上放入我們自定義的資料屬性,
    要注意的是,屬性名不可以是大寫字母,前面需要加上前綴詞data-緊接至少一個小寫字符,如我們用的key成了我們自定義的data-key標籤,例如<audio data-key="55">

CSS 的部分

  • 我們先把要出現的效果寫好,命名為.key樣式。
  • 當我們按下按鍵時,加上.playing樣式 讓畫面上的按鍵有些效果。這個.playing 樣式會在離開這個鍵時被拿掉。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.key {
border: 0.2rem solid rgb(11, 89, 37);
border-radius: 0.5rem;
margin: 1rem;
font-size: 1.5rem;
padding: 1rem 0.5rem;
transition: all 0.07s ease;
width: 100px;
text-align: center;
color: rgb(21, 168, 40);
background: rgba(255, 254, 254, 0.6);
text-shadow: 0 0 0.5rem rgb(15, 15, 1);
}

.playing {
transform: scale(1.1);
border-color: rgb(21, 168, 40);
box-shadow: 0 0 1rem rgb(21, 168, 40);
}

JavaScript 的部分

前面有提到,當我們按下按鍵,程式會去呼叫發出音效的playSound()函式,做到這一點,我們得先監聽這些按鍵,何時被按下繼而觸發事件。

我們先將所有的按鍵放在一個陣列型別的變數keys裡,然後利用forEach()走訪陣列裡的元素(鍵),並監聽所會發生的事件,給予事件一個名稱:transitionend,並在觸發時,執行removeTransition函式去清除按下去被觸發的行為。也就是使鍵盤的 Key 恢復還沒被按下去的原狀。

1
2
3
4
5
const keys = Array.from(document.querySelectorAll('.key'));
keys.forEach((key) => key.addEventListener('transitionend', removeTransition));

// 監聽頁面的鍵 keydown事件,觸發 playAudio 函式。
window.addEventListener('keydown', playSound);

接著我們來看看發出音效的函式playSound(event):

  • 利用querySelector先找出「音效」和「鍵」的 DOM 元素。
  • 如果沒有找到audio就什麼也不做。
  • key上面加上動畫效果的playing樣式。
  • 讓播放音效的時間歸零。
  • 再開始播放。
1
2
3
4
5
6
7
8
9
10
function playSound(event) {
const audio = document.querySelector(`audio[data-key="${event.keyCode}"]`);
const key = document.querySelector(`div[data-key="${event.keyCode}"]`);
if (!audio) return; // arreter le fonction de jouer

key.classList.add('playing');
audio.currentTime = 0; // revenir au debut
// key.currentTime = 0; // revenir au debut
audio.play();
}

按鍵的監聽+音效的播放+按下鍵的動畫效果

還有,在按下按鍵發出聲效後,使鍵盤的 Key 恢復原狀,移除這個動畫效果的removeTransition(event)函式:

  • 先檢查這個要監聽的事件元素,如果沒有 CSS 的動畫屬性transform就什麼都不做。
  • 否則,就在這個元素上移除這個.playing CSS 樣式。
1
2
3
4
function removeTransition(event) {
if (event.propertyName !== 'transform') return;
event.target.classList.remove('playing');
}

需要注意的是,我們必須把寫好的函式放在 JS 程式碼的最前面,因為程式是一行行讀的,如果沒有先放前面,在讀到呼叫這些函式時,程式會因為還沒記憶這些函式而出現錯誤。

相關語法

在這個練習裡的JavaScript,我們用到了一些 ES6 的語法:

  • 使用了const的變數宣告,表示常數只能賦值一次。也就是說,賦值給這個變數後,值就不能再更改了。
  • 使用了字串模板(Template literals),語法為:字串 ${變數、屬性名}樣板字面值 MDN
  • forEach和箭頭函式:因為在這個練習裡使用的是document.querySelector來得到一組 NodeList(DOM),使用forEach方法來走訪這些 DOM 元素。

基礎語法

解決問題

如何可以讓我們按下鍵盤某個鍵時,和音效連結起來? 可以連結的原因是在keydown的事件中的keyCode屬性,keyCode的屬性值和 ASCII
的編碼相同,我們可以在 JavaScript Event KeyCodes 查找對應的鍵碼。
我們在按鍵的div和音效的audio標籤中,都增加了一個我們自定義的屬性data-key,這樣我們在按下按鍵的同時,也可依循找到對應的音效。

如何讓按鍵即使按著不放,也可以馬上有連續的音效? 我們把每次要播放音效之前,將播放的時間點歸零。

如何讓每次按完鍵後,恢復成原狀? 在監聽按鍵時,我們使用transitionend這個事件,讓它在動畫效果結束後被觸發,動畫效果完成之後,就去除這個樣式。但是因為按鍵按下時,不只只有動畫效果transform所以需要增加一個if的判斷式,讓每發生一次按鍵事件,只去除一次樣式。

碎碎唸:其實如果沒有這個判斷式,如果按住按鈕不放,也不會有一直是有動畫效果停滯,反而效果較好。

後記

雖然看似步驟不複雜的按鍵與音效,要注意的部分也不少,希望在這樣的詳細拆解之下,可以多了解不同語法的應用與使用的原因,以實務的方式了解程式面,的確比較類比多了。