「JS30紀錄&心得」01 - JavaScript Drum Kit

主題

透過JS使鍵盤按下後播放出對應按鍵的聲音,並同時產生一個特效,
在按下其他鍵後會關閉該特效並於新按鍵中啟用。

[DEMO][GitHub][JavaScript30全列表]

步驟

Step1. 新增keydown listener

利用window.addEventListener('keydown', playSound);來監聽鍵盤動作。

Step2. 建立functionplaySound

  1. 利用傳入的e.keyCode來取得對應的audio標籤及該按鍵的div標籤
  2. 判斷傳入的e.keyCode是否有對應的audio標籤,若無則退出
  3. 使對應的div加上playing樣式,產生對應的典及特效
  4. 使對應的audio播放時間為0
  5. 播放對應的音檔

Step3. 新增transitionend listener

  1. 偵測所有包含className='key'的元件
  2. 當該元件觸發特效並結束時(transitionend),呼叫removeTransition

Step4. 建立functionremoveTransition

  1. 判斷傳入的propretyName是否為transform,若否則退出
  2. 若為transform,則移除playing樣式

JavaScript語法&備註

element.classList

這個會回傳element的class值(陣列),
範例用到了classList的方法add()remove()

1
2
classList.add('aaa', 'bbb', 'ccc'); //新增多個className
classList.remove('aaa', 'bbb', 'ccc'); //移除多個className

如果已經存在/不存在的className則會被忽略。

還有其他方法如:
toggle()偵測是否存在這個className,存在則刪除/不存在則新增
contains()偵測是否存在這個className, 返回true/false
參閱:MDN-Element.classList

HTMLmediaElement(audio)

HTML的audio標籤,在HTML放置如下標籤指定音源

1
<audio src="sound/a.mp3"></audio>

透過javascript來操作:
element.play():進行播放
element.currentTime:指定播放秒數
範例中使用currentTime是為了達到連發的效果XD

參閱:MDN-HTMLMediaElement

forEach

之前沒在javascript中使用的語法,用法如下:

1
arr.forEach(callback function)

我是用for迴圈來比對做語法理解的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let datas = ['data1', 'data2', 'data3'];
//for迴圈寫法
for (let i = 0; i < datas.length; i++) {
console.log(datas[i]);
}
//forEach寫法
datas.forEach(function(data){
console.log(data);
});
//都會輸出
//data1
//data2
//data3

datas.forEach(console.log);
//如果透過上面直接console.log來看到結果是:
//data1 0 ["data1", "data2", "data3"]
//data2 1 ["data1", "data2", "data3"]
//data3 2 ["data1", "data2", "data3"]
//回傳的分別是value, index, array本身內容。

參閱:MDN-Array.prototype.forEach()

箭頭函式(Arrow Function)

ES6的新語法

1
2
3
4
5
6
//傳統寫法
let func1 = function(arg) { console.log('Hi, ' + arg); };
//箭頭函式寫法
let func2 = arg => console.log('Hi, ' + arg);
//補充:如果該function沒有參數要傳,要帶空括號如下
let func3 = () => console.log('Hi');

參閱:MDN-Arrow functions

addEventListener

因為我是是第一次看到transtionend這個event,
所以去MDN查了HTML DOM event記錄連結在此

參閱:MDN-Event reference

template literals

模板文字,同樣屬於第一次看到的東西,
利用` - 反引號(back-tick)或稱重音符(grave accent)來組合字串,
在範圍內可利用${}加上變數操作

例如原本的字串+變數組合寫法:

1
2
3
let str = '<div data-key="' + key + '">' +
'<button>click me</button>' +
'</div>';

改用template string來做只要

1
2
3
let str = `<div data-key="${key}">
<button>click me</button>
</div>`;

` 包住字串,利用${}來包變數
這樣可以很輕鬆的組出易於閱讀的組合字串!
不用像以前還要注意單雙引號與+的配合了~

參閱:MDN-Template literals

Array.from

範例中有這段

1
const keys = Array.from(document.querySelectorAll('.key'));

查詢了Array.from才知道這是一個將一個物件或是字串轉為陣列格式的語法,
但當時覺得為何要把陣列在轉成陣列?querySelectorAll不就是返回陣列嗎?
在查下去才發現querySelectorAll返回的是nodeList且nodeList跟Array是不同的!
雖然都很像陣列,但nodeList並沒有array.prototype上的方法!
最簡單的例子是用array.push()去測試,會發現由querySelectAll得到的物件無法用.push()。

1
2
3
4
5
let testNodeList = document.querySelectAll('.key');
testNodeList.push('add'); // <--非陣列會報錯TypeError: testNodeList.push is not a function

let testArray = Array.from(testNodeList);
testArray.push('add'); // <-- 轉為陣列就可以了

至於在範例中轉型的原因,
我想應該是因為若無轉型為Array使用nodeList來forEach可能會導致某些瀏覽器版本錯誤。

nodeList由querySelectorAll及childNodes返回的
參閱:MDN-NodeList

CSS語法&備註

display:flex

CSS3的排版語法,以範例中的來做備註紀錄

1
2
3
4
5
6
7
.keys {
display: flex; /*要使用flex要在元素內先宣告flex*/
flex: 1; /*這是一個簡寫,全部為flex: flex-grow|flex-shrink|flex-basis*/
min-height: 100vh; /*vh代表view height, 百分比呈現*/
align-items: center; /*宣告為flex後才有效的屬性,垂直置中*/
justify-content: center;/*宣告為flex後才有效的屬性,水平置中*/
}

參閱:MDN-flex

探索

原範例只能由鍵盤觸發,
我的探索是為這個範例加上可由滑鼠點擊觸發的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const keys = Array.from(document.querySelectorAll('.key'));

//新增click功能綁定至每個class="key"
keys.forEach(key => key.addEventListener('click', playSound));

function playSound(e) {
//依據不同的事件來取得對應的key_code(e.type可以看,以下是簡寫版)
let keyCode = e.keyCode || this.getAttribute('data-key');

const audio = document.querySelector(`audio[data-key="${keyCode}"]`);
const key = document.querySelector(`div[data-key="${keyCode}"]`);

if (!audio) return;

key.classList.add('playing');
audio.currentTime = 0;
audio.play();
}