JS30的第一天
要將鍵盤變成一個爵士鼓,首先看一下所拿到的.html檔
<div class="keys">
<div data-key="65" class="key">
<kbd>A</kbd>
<span class="sound">clap</span>
</div>
<div data-key="83" class="key">
<kbd>S</kbd>
<span class="sound">hihat</span>
</div>
.
.
.
</div>
data-*
是用來存放與該元素有關的小型資料,可以自己給屬性值,一個tag可以有許多data-*
值
接著我們要監聽鍵盤被按下這個keydown
事件
window.addEventListener('keydown', playSound);
function playSound(e) {
console.log('鍵盤被按下');
};
由於window是包著DOM的物件,所以可以偵測到keydown事件,並觸發functionplaySound
值得注意的是這裡不能使用keypress
原因在此
接著我們要播放音樂了,可以在參數內放一個e
去console.log(e)
看看keypress
事件,並可以看到一些值在裡面供我們使用,要知道「哪一顆按鈕被按下」,keyCode
正是我們的目標
function playSound(e) {
let audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
if (!audio) return;
audio.play();
};
playSound(e)
傳入監聽事件的物件,在宣告函式時,通常用參數e來表示event,去接收該事件
這裡的e
會接收keydown事件
document.querySelector()
DOM的選取方法,()內填入CSS selector組成的字串
如果按a,s,d,f,g,h,j,k,l這九個按鈕,會得到相對應的標籤,其他按鈕則會回傳null
因為null
的布林值是false
Boolean(null); //false
因此如果是null
,就直接return
終止函式
if(!audio) return;
剩下的就播放audio
但此時連點按鍵卻無法連續的打擊鼓面,因為上一次點擊所播放的聲音還沒有結束
因此,我們要讓每次函式.play()
前,使用.currentTime
將進度條調整至0:00,這樣就可以連續敲擊了
function playSound(e) {
let audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
if (!audio) return;
audio.currentTime = 0;
audio.play();
};
聲光效果中的聲音已經完成,接下來是光的效果了
首先,我們先看看css怎麼寫的
.key {
border: .4rem solid black;
border-radius: .5rem;
margin: 1rem;
font-size: 1.5rem;
padding: 1rem .5rem;
transition: all .07s ease; /*這個是動畫的時間*/
width: 10rem;
text-align: center;
color: white;
background: rgba(0,0,0,0.4);
text-shadow: 0 0 .5rem black;
}
.playing {
transform: scale(1.1); /*會放大元素並改變border顏色*/
border-color: #ffc600;
box-shadow: 0 0 1rem #ffc600;
}
我們先監聽的.key
,並使用[data-key=e.keyCode]
來確認取到的key
並使用.classList.add()
新增class。.classList.add()
可以讓你包含原本的class,並新增想要的class
function playSound(e) {
let audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
let key = document.querySelector(`.key[data-key="${e.keyCode}"]`);
if (!audio) return; //keydown without a,s,d,f,g,h,j,k,l
audio.currentTime = 0; //set the currentTime to start
audio.play();
key.classList.add("playing");
};
我們會發現螢幕上對應的按鈕會亮了,但是不會消失,可以使用setTimeout()
,隔一段時間後再執行函式
setTimeout(function(){
key.classList.remove("playing");
},70)
但是這個方法需要一直去看transition
過了多久
再設定時間過多久才開始執行
所以我們可以偵測另一個事件transitionend
就會等到transition
結束才執行該函式
方法如下:
let keys = document.querySelectorAll('.key');
console.log(keys); //NodeList(9)[div.key, div.key, div.key, div.key, div.key, div.key, div.key, div.key, div.key];
.querySelectorAll
會將所有的.key
以一個list顯示,這個list長得很像array
但是他只是個類陣列array-like,並不是一個真的陣列
所以不能使用array特有的method
而這裡所產生的是Nodelist
剛好NodeList
也有支援.forEach
,所以可以使用
let keys = document.querySelectorAll('.key');
keys.forEach(key => key.addEventListener('transitionend', removeTransition));
傳入類陣列內的每個元素去監聽它們,等到transitionend後才執行removeTransition
function removeTransition(e) {
if (e.propertyName !== "transform") return;
this.classList.remove("playing");
};
如果這時候console.log(e)
會發現跑出一大串
其實這是對transform
、border-color
、box-shadow
等等的屬性
因為新增了.playing
所產生的transition
當轉場結束後
會觸發很多個transitionend
這時候我們要只需要其中一種propertyName即可
因此將非transform
的propertyName濾掉
最後使用this
指定到.key
並移除.playing
也有找到方法是使用e.target
來指定
function removeTransition(e) {
...
e.target.classList.remove("playing");
};
e.target
永遠會指向觸發event的DOM元素this
則是指向掛addEventListener的元素
用這種方式也可以刪除.playing
這樣就大功告成了!
參考資料