2017年1月21日

[筆記] JS30系列:監聽按鍵事件及撥放音效(Day1)


此課程內容來自 Wes Bos ,文章內容為整理課程內容學習之筆記,如果直接看筆記的話可能會覺得有點跳,不適合直接當成單篇文章閱讀,因此建議可以先看過該堂課程內容後,需要複習的時候在回來看筆記。關於此課程的上課說明可參考 JavaScript30 課程說明

第一堂課完成的頁面會像這個網站所顯示的樣子,當按下畫面上的所對應的按鍵時會有對應的動畫效果和音效。

思考分析


1. 瀏覽器能夠辨認使用者在鍵盤上所按下的按鍵
2. 當鍵盤按下指定的按鍵後能夠觸發音效
3. 當鍵盤按下指定的按鍵後能夠觸發網頁上的元件產生相對應的變化
4. 網頁上元件的變化要能夠回覆成原本的狀態
5. 播放的音效要能夠停止

辨認使用者在鍵盤上所按下的按鍵


KeyCode:每一個按鍵其實都有對應的鍵碼(keycode),而我們可以透過使用者所按下的鍵碼來得知使用者是按了鍵盤上的哪一個按鈕。在 keycode.info 這個網站中,你就可以得到每一個按鍵所對應到的鍵碼。

keycode @ keycode.info

addEventListener:在 JS 中,我們要監聽事件的話通常會用到 addEventListener,在這裡也不例外,為了要監聽使用者按了那個按鍵,我們會寫這樣子:


// 監聽鍵盤按鍵事件,並回傳所按的按鍵為何
  window.addEventListener('keydown', function(e){
    console.log(e);
  });


這裡我們用的是 keydown 事件,後面可以帶函式 function(e){...}),函式的參數中會預設回傳一個屬於該事件引發後的物件,在這裡我們用 console.log 來看看它會回傳什麼事件。

假設我在網頁上點選 "A",這時候它會回傳一個物件長這樣,其中包含了像是我們按了哪個按鍵的屬性(key),也包含這個按鍵的鍵碼(keyCode)等等:


在這裡 keyCode 會是很重要的屬性,有了它我們就知道使用者按了鍵盤上的哪個鍵,然後我們針對是個按鍵提供網頁上相對應要產生的效果。

鍵盤按下指定按鈕後能夠觸發音效


在來你會看到課程的 HTML 文件中,不論是 button 或 audio 標籤都加入了 data-* attribute,這裡它用的是 data-key 像是這樣:


data-* attribute:簡單說明一下什麼是 data-* attribute,有時我們會需要使用到某些自定義的屬性,但是為了要避免大家在 HTML 中隨意的添加屬性,於是在 HTML5 中就多了 data-* attribte 這個屬性,其中的 * 就是一個可以自定義的名稱,例如:data-key='83' 或者是 data-item='1',關於 data-* attribute 屬性如果想要有更多的瞭解,可以參考([教學] 什麼是 HTML 5 中的資料屬性)。

有了 data attribute 我們就可以選擇到和按鍵所對應的音效,並使他播放。但我們要做的是要先選到和按鍵所對應的音效元素,這時候我們會需要用到 JavaScript 中的 querySelector

querySelector:這是 JS 中原生的 CSS 選擇器,裡面可以像 jQuery 一樣去選擇想要選擇到的元素,有一些要注意的地方像是,若找不到相對性的元素會回傳 null,否則會回傳第一個符合的元素,因此,若你是多個屬性具有相同的 class 名稱時,它只會選到第一個符合的元素。如果你是想要選擇多個具有相同 class 名稱的屬性時,就要用 querySelectorAll
瞭解更多關於 querySelectorquerySelectorAll

下面這段程式碼的意思,就是當我按了鍵盤上的按鍵後,幫我選擇相對應的 keyCode 元素,否則的話就會回傳 null 。


//  選擇鍵盤按鍵所對應到的網頁元素
window.addEventListener('keydown',function(e){
    const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
    console.log(audio);
})


在下面這段程式碼中還有一個可以留意的是在 querySelector( ) 裡面,使用了 ES6 中的模版語言,它是使用 ` 符號(鍵盤左上角的~)來將,在模版字串中如果有需要引入變數,則可以是用${ },來代入變數(若不清楚模版字符串的使用,可參考[筆記] JavaScript ES6 中的模版字符串和標籤模版)。

當我們按鍵按到了相對應 keyCode 時就可以選到該元素,否則就會回傳null


當我們能夠找到指定的元素時,我們就可以透過 JS 叫它播放,要讓它播放的方式很簡單,只要在找到的這個元素後面寫 .play() 就可以了。

另外,為了避免當我們沒有按到頁面上所指定的按鍵時,會回傳錯誤結果,我們多一行 if(!audio) return,意思是如果沒有按到指定的按鍵,則透過 return 來停止這個函式,像是這樣:


//  播放元素音效檔
window.addEventListener('keydown',function(e){
    const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
    if(!audio) return;  //  如果沒有按到對應的按鍵,則停止此函式
    audio.play();       //  播放元素的音效
})


做到這裡當你下所對應的按鍵時,就會發出音效了。但我們會碰到另一個問題,就是當我們快速連續點擊同一個按鍵時,原本我們預期音效應該會快速重複,但實際上卻沒有這樣的效果。

之所以會有這個問題,是因為一個音效檔的時間長度可能是 3 秒,當我們使用 .play( ) 來觸發音效時,在音效檔還沒撥到結束的時候,它不會再重複執行一次。

解決這個問題的方法,就是讓函式每次執行的時候,當讓音效檔的時間點回到一開始,這樣它才會重新播放,寫法是利用到 currentTime 這個屬性,它可以使用在 HTML 中 audio 或是 video 標籤(參考:W3Schools),寫法像是下面這樣:


window.addEventListener('keydown',function(e){
    const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
    if(!audio) return;      //  如果沒有按到對應的按鍵,則停止此函式
    audio.currentTime = 0;  //  讓每次回到音效檔的起始點
    audio.play();           //  播放元素的音效
})


到這裡如果沒問題的話,你按下對應的鍵應該就會發出對應的音效了。

鍵盤按下指定按鈕後能夠網頁動畫效果


接下來我們要做的,就是讓鍵盤按下指定的按鈕後,能夠讓網頁產生動畫效果,而這個動畫效果之所以會產生,是因為在指定的元素上,加了特定的 class 之後再移除所造成的。

所以和剛剛一樣,我們要先監聽使用者鍵盤按下的按鍵,接著取的他所按下按鍵所對應到的網頁元素:


//  監聽使用者按鍵事件,並取得對應的元素
window.addEventListener('keydown', function(e){
    const key = document.querySelector(`.key[data-key="${e.keyCode}"]`);
})


我們可以看到,在作者的 CSS 程式碼中,原本按鍵只套用 .key ,當我們按下指定的按鍵時,則會多套用 .playing 這個 class 來達到動態的效果。

另外,這裡也有用到 CSS 中的 transition 屬性,除了讓動畫的改變更滑順外,我們後面也會利用它的功能,來自動的拿掉 .playing 這個屬性。



classList 物件:接下來我們要做的就是讓按下按鍵之後,能夠在該元素上添加指定的 class 以產生動態的效果,可以利用 JS 中內建的 element.classList.add( ) 這個方法:


window.addEventListener('keydown', function(e){
    const key = document.querySelector(`.key[data-key="${e.keyCode}"]`);    //  取得按鍵對應的元素
    key.classList.add('playing');       ///為該元素增加 class
})


現在當我們按下按鍵後,他就會自動加上 .playing 這個 class,進一步的問題是,要怎麼讓它自動移除這個 class ,否則這個動畫產生的效果不會消失。

transitionend event:這個 transitionend 事件主要是對應到 CSS 中 transition 的動畫效果,當這個 transition 效果執行結束的時候會引發事件。

所以我們能做的就是監控每一個按鍵的 transitionend event ,一旦我們偵測到這個動畫效果(transition)已經結束,那麼我們就可以把原本添加的 .playing 這個 class 拿掉。

querySelectorAll:為了監聽每一個按鍵的事件,這裡我們就會用到 querySelectorAll 這個方法,它會選出所有具有該 class 的元素,並回傳為一 NodeList

forEach( ):接著為了要重複的讀出這個 NodeList 的內容,我們會用到 forEach( ) 這個方法,forEach( ) 這個方法裡面可以放入 callback function,預設的參數包含該 NodeList 的屬性值和屬性名稱。因此我們可以寫出如下的語法:


const keys = document.querySelectorAll('.key'); //  選出所有具有 .key 的元素,回傳成陣列
keys.forEach((key) => {key.addEventListener('transitionend', removeTransition)});   //  利用 forEach 方法讀取 NodeList 並為每一個元素加上 transitionend 事件,事件執行時會促發 removeTransition 


接著我們就要來撰寫 removeTransition 的函式,這個 removeTransition 就是 addEventListener( ) 當中的函式,所以它一樣可以回傳一個屬於該事件的物件,這裡我們把它稱做 e:


let removeTransition = function(e){
    console.log(e);
}


我們來看一下當我們按下按鍵後,會回傳什麼內容:


這落落長的到底是什麼東西呢?我們可以在圖片中看到 propertyName 的地方分別有 border-*-color, border-*-width, 另外還有一個 transform,這些東西是當 transition 效果結束之後會回傳的內容,在這裡我們就拿 propertyName = transform 這個來當指標,當transform 出來(也就是 transition 結束,transitionend 被觸發時),把 .playing 這個 class 移除。


let removeTransition = function(e){
    if(e.propertyName !== 'transform') return; //    省略掉其他 propertyName 不是 transform 的物件
    this.classList.remove('playing');   //  把 playing 這個 class 移除
}


如果你不清楚 this 是什麼可以參考這篇:[筆記] 談談JavaScript中的"this"和它的bug,或者直接透過 console.log(this) 把它呼叫出來看看就會更明白了。

到這裡我們就把所有需要用到的效果都寫完拉!

看一下我們學到了什麼


  • 監聽事件(addEventListener(event, function))--- 在 function 中代入參數會回傳和該事件有關的物件
  • 按鍵事件(keydown
  • 透過 CSS 選擇元素(querySelector, querySelectorAll)
  • 音效相關(element.play( ), element.currentTime)
  • 資料屬性(data-* attribute
  • CSS相關(element.classList.add( ), element.classList.remove( )
  • 動畫結束事件 --- transitionEnd
  • forEach( )
  • 若不符合則退出函式:function( ){if(...) return}

如果想要看一下簡單的 DEMO 的話,可以到 CodePen 看看喔!



資料來源:

0 意見:

張貼留言