竹白記事本,Javascript 30,紀錄。
Javascript 30
模擬一個打鼓的頁面。用戶在鍵盤上按下 ASDFGHJKL 這幾個鍵時,頁面上與字母對應的按鈕變大變亮,對應的鼓點聲音響起來。
classList
屬性
add
方法remove
方法addEventListener()
事件監聽
keydown
事件transitionend
事件KeyboardEvent
介面
keyCode
屬性TransitionEvent
介面
propertyName
屬性Event
介面
currentTarget
屬性target
屬性在 HTML 中,已經使用 data-key
,定義了對應的 keyCode
屬性。
當使用鍵盤相關事件時,event
物件具備一個 keyCode
屬性,可以得知當下所按下的按鍵為何按鍵按鈕。它回傳的值是 ASCII
而鍵盤事件的差異:
kepdown
keyCode
keypress
keyCode
值keyup
keyCode
的部分與 kepdown
相同觸發優先順序由上到下,keydown
→ keypress
→ keyup
。
JavaScript Event KeyCodes 查看鍵盤對應的
keyCode
。
使用 currentTime
將聲音歸 0
。
當按下按鈕的動畫結束時,移除樣式的 classname
。
所以要使用 transitionend
事件的 event
物件所繼承的 propertyName
屬性來判斷,特定 CSS 屬性已完成動畫顯示。
這裡要注意,CSS 屬性不只一個在樣式變化,所以一定要指定特定 CSS 屬性,否則會一直重複觸發,你所要執行的動作。
關於鍵盤、卷軸、螢幕旋轉等,基本上都會在 window
下進行事件監聽。
因此這邊在 window
下新增一個監聽 keydown
事件,當事件發時觸發一個函式。
window.addEventListener('keydown', playHandler);
playHandler
宣告函式:
function playHandler(e) {}
通常用參數 e
代表 event
,e
會接收 keydown
事件。
這個函式需要做兩件事:
HTML 已經用 data-key
寫好對應的 keyCode
元件,因此只要透過 querySelector
來綁定就好了。
const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
const dom = document.querySelector(`div[data-key="${e.keyCode}"]`);
這裡使用 ES6 的樣板字面值(Template literals)來選取,如果不使用就是:
'audio[data-key="' + e.keyCode + '"]'
'div[data-key="' + e.keyCode + '"]'
再來為了防止按下其他按鍵而找不到對應的元件時出錯,因此要加入判斷式。
使用肯定語句,將內容寫在裡面,或是否定語句,找不到直接離開,將內容寫在下方。
if (audio) { ... }
if (dom) { ... }
// or
if (!audio) {return;}
if (!dom) {return;}
...
...
撥放音樂與新增 CSS 樣式:
audio.currentTime = 0; // 每次撥放音樂都重頭開始
audio.play(); // 播放
dom.classList.add('playing'); // 新增 .playing 類別
最終 playHandler()
函:
function playHandler(e) {
const audio = document.querySelector(
`audio[data-key="${e.keyCode}"]`
);
const dom = document.querySelector(
`div[data-key="${e.keyCode}"]`
);
if (audio) {
audio.currentTime = 0;
audio.play();
}
if (dom) {
dom.classList.add('playing');
}
}
transitionend
事件監聽.playing
類別是一個效果,每當觸發完後,必須移除畫面效果,因此必監聽所有的畫面元件,查看動畫是否結束了。
document.querySelectorAll('.key').forEach(function(key) {
key.addEventListener('transitionend', removeTransition);
});
querySelectorAll
得到的是一個 NodeList
不是陣列,但 NodeList
還是有 forEach()
方法可使用。為每個元件加入監聽 transitionend
事件。
注意,這裡是因為剛好 NodeList
有 forEach()
,如果是 map()
還是其他陣列方法,NodeList
物件沒有的方法,一定要將它轉成陣列成能使用陣列的方法。
removeTransition
transitionend
事件如果有很多屬性,會重複觸發,因此需要挑一個屬性來判斷是否完成,這裡選擇 transform
屬性來判斷,如果完成了就移除 .playing
類別。
function removeTransition(e) {
if (e.propertyName === 'transform') {
e.currentTarget.classList.remove('playing');
}
}
這裡使用 target
或 currentTarget
都可以,但兩者也些微差異。
currentTarget
是擁有監聽事件的 DOM 物件,而 target
是觸發事件的 DOM 物件。
因此如果使用事件委派就要注意,關於事件委派將在下方試著改寫此範例。
(function() {
function playHandler(e) {
const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
const dom = document.querySelector(`div[data-key="${e.keyCode}"]`);
if (audio) {
audio.currentTime = 0;
audio.play();
}
if (dom) {
dom.classList.add('playing');
}
}
function removeTransition(e) {
if (e.propertyName === 'transform') {
e.currentTarget.classList.remove('playing');
}
}
window.addEventListener('keydown', playHandler);
document.querySelectorAll('.key').forEach(function(key) {
key.addEventListener('transitionend', removeTransition);
});
})();
將步驟 3 與 4 改成事件委派,可以減少監聽事件。
function removeTransition(e) {
// 1
if (!e.target.classList.contains('key')) {
return;
}
if (e.propertyName === 'transform') {
// 2
e.target.classList.remove('playing');
}
}
const keys = document.querySelector('.keys');
keys.addEventListener('transitionend', removeTransition);
if (!e.target.classList.contains('key')) { return; }
用來判斷觸發的 DOM 物件是否擁有 .key
類別,如果沒有就跳出。target
來判斷觸發的 DOM 物件,而不是本來使用的 currentTarget
因為監聽事件改用父元素了。新增使用滑鼠點選畫面也能觸發音樂的監聽器。
const keys = document.querySelector('.keys');
// 1
keys.addEventListener('click', clickHandler);
function clickHandler(e) {
if (!e.target.classList.contains('key')) {
return;
}
// 2
const keyCode = e.target.dataset.key;
const audio = document.querySelector(`audio[data-key="${keyCode}"]`);
// 3
audio.currentTime = 0;
audio.play();
e.target.classList.add('is-playing');
}
.keys
,在一樣使用事件委派模式。event
物件沒有 keyCode
屬性,所以可以利用 dataset
來取得對應的 keyCode
值。這裡有一個地方要注意,因為使用事件委派,所以只有點選擁有 .key
類別的 DOM 物件會觸發,所以必須將 .key
底下的子、子孫元素都虛擬化,避免點選到。
.key * {
pointer-events: none;
}
or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Do you want to remove this version name and description?
Syncing