最新消息:XAMPP默认安装之后是很不安全的,我们只需要点击左方菜单的 "安全"选项,按照向导操作即可完成安全设置。

Day 1 – JS Drum Kit

XAMPP下载 admin 1129浏览 0评论
 任務內容
利用JS做出一組爵士鼓.
HTML code如下, 類別keys內部包著九個類別為key, 但持有不同data-key屬性值的div標籤. 在外頭也有九個分別與之對應的audio標籤. 當與kbd標籤內部對應的按鍵被按下時, 會發出相對應的聲響.

<div class=”keys”>
<!–keys裡面包著key–>
<div data-key=”65″ class=”key”>
<kbd>A</kbd>
<span class=”sound”>clap</span>
</div>
</div>

<!–與key對應的audio標籤–>
<audio data-key=”65″ src=”sounds/clap.wav”></audio>

作法
首先在window物件底下設置監聽keydown事件的監聽器, window物件為包覆整個DOM的物件, 在該處設置監聽器可以監聽DOM內觸發的所有事件. keydown事件只要瀏覽器偵測到鍵盤被按下的瞬間就會觸發. 被觸發的瞬間, 執行自訂函式playSound來回應.

window.addEventListener(‘keydown’, playSound);
playSound函式就是用來播放聲音的! 要發出指定的聲音需要幾個步驟, 以按鍵’A’被按下的過程來舉例:

鍵盤的’A’慘遭按下
存取data-key屬性中帶有’A’的keyCode的audio元素, 如果沒有找到目標, 直接停止動作並返回
如果有找到, 播放該audio元素連結的聲音檔
按照以上的邏輯, 寫出來的程式碼大致如下:

function playSound(e) {
const audio = document.querySelector(`audio[data-key=”${e.keyCode}”]`);
if (!audio) return;

audio.play();
}
看起來有點複雜, 其實也還行! 拆開來個別理解一下…

playSound(e)
addEventListener會把被監聽的事件物件當成值, 傳入回應的自訂函式之中. 要讓自訂函式收到該值, 可以在宣告自訂函式時提供一個代表該事件的參數, 通常會用e (或是event)命名. 在這裡playSound(e)裡面的e代表keydown事件的物件

e.keyCode
e.keyCode屬性記錄著”按下的按鍵”的keyCode. 鍵盤上的每個按鍵都有一個相對應的keyCode, 有興趣可以在 http://keycode.info/

看看各個按鍵的keyCode. 以’A’舉例, 就是65.

document.querySelector
document.querySelector() 是用來選取DOM的方法, 括號內部填入代表CSS選擇器的字串.

CSS Selector
CSS Selector 是一組用來選擇特定元素的CSS 符號. `audio[data-key=”65″]` 就是一串CSS selector. 在CSS Selector的意思為具有屬性data-key=”65″的audio標籤.

Template Literal
Template Literial是在字串前後以` `代替” “, 通常內部放的東西會被當成字串, 若要插入變數只要以${變數}隔開就好. 使用 Temlate Literial讓”在字串中插入變數”的動作更加容易.

我們希望 data-key 的值是每次按下去的按鍵的keyCode, 而不是固定的65, 因此需要在data-key的值放進代表按鍵輸入keyCode的變數. 結合CSS Selector 和 Template Literal, 可以寫成 `audio[data-key=”${e.keyCode}”]`.

data-* 屬性
data-key屬性是自訂的 data-* 標頭屬性, 這種屬性通常用來儲存與該元素標籤相關的小型資料, 一個標籤可以有好幾個 data-* 屬性.

所以下面這段程式碼的意思就是, 找到data-key屬性中存著按下按鍵keyCode的audio標籤, 並指定給audio這個變數.

const audio = document.querySelector(`audio[data-key=”${e.keyCode}”]`);
根據原始檔提供的HTML標籤, 只有九個按鍵會有相對應的audio標籤. if(!audio) return 的意思為: 如果沒有找到對應的audio, 就返回.
if() 括號內的東西會自動被轉成Boolean, 結果只有true或false, 除了falsy以外的東西, 放進去的結果都是true. , 如果按下去的按鍵沒找到相對應的HTML標籤, audio變數的值就會是 undefined , 是falsy的一種, !audio就是非audio.
如果沒有被返回, 表示有該標籤, 就可以在下一行用audio.play()播放.

大致上完成了! 但有些不流暢的小地方!
實際打鼓後會發現, 有些比較長的聲音檔在播放時, 當新按鍵按下去, 新的聲音檔並不會被立即執行! 所以我們的鼓會有點lag, 沒辦法咚咚咚一直敲, 這簡直侮辱了鼓手的尊嚴!
所以我們得加點料…

function playSound(e) {
const audio = document.querySelector(`audio[data-key=”${e.keyCode}”]`);
if (!audio) return;
// 在 play 之前加入這行
audio.currentTime = 0;
audio.play();
}
在audio.play()上方加了一行audio.currentTime = 0. currentTime特性代表目前播放的進度.每次播放聲音前, 將播放進度設定回原點, 然後再播放, 這麼一來就可以連續敲打了!!

但還是有個美中不足之處, 我們希望敲打時, 被敲打的按鍵會發光並放大, 讓我們知道自己正在敲打哪個樂器, 沒錯, 這就是身為鼓的使命.
所以可以…

選取所有具有key類別的標籤
在選取的標籤上設下監聽器: 如果該標籤被按到了, 就發光變大!
然後再變回去
因此得加入一些程式碼!

function playSound(e) {
const audio = document.querySelector(`audio[data-key=”${e.keyCode}”]`);
// 用一樣的方式找尋具有相同data-key的div元素
const key = document.querySelector(`div[data-key=”${e.keyCode}”]`);
if (!audio) return;

// 在代表按鍵圖示的標籤上直接加上 playing 類別
key.classList.add(‘playing’);
audio.currentTime = 0;
audio.play();
}
用相同的方式選取具有對應data-key的div元素, 如果具有該HTML元素, 就為它加上’playing`這個CSS類別. playing這個類別記錄了放大和邊緣發黃光的CSS! 加上的瞬間, 它就會發光!

.playing {
transform: scale(1.1);
border-color: #ffc600;
box-shadow: 0 0 1rem #ffc600;
}
但總不能一直讓它發著光, 所以必須設定發完光就光芒退散.
此時先看一下key類別的CSS.

.key {
/* 前略… */
transition: all .07s ease;
/* 後略… */
}
transition 是CSS轉場, 第一個參數代表變化時會使用到轉場的屬性, all就是全部, 表示.key內可以支援轉場效果的任何屬性, 只要發生變化, 都會以動畫的方式漸變到新的屬性去. 第二個參數代表在多少時間內要完成轉場,第三個參數代表轉場過程與時間相依的函數, 簡單來說轉場速度不會是固定的, 可以依照設定在一開始轉變得很快, 後來變很慢…之類的.
會特別提到轉場, 是因為轉場結束後會觸發一個transitionend事件, 是我們要監聽的對象! 是的長官, 發現目標了!
在keydown監聽器上方加上這兩行!

const keys = document.querySelectorAll(‘.key’);
keys.forEach(key => key.addEventListener(‘transitionend’, removeTransition));
用document.querySelectorAll(‘.key’)把所有帶有key類別的元素都選起來, 並指定給key變數. key變數內所存的值會是一串清單(DOM List), 內容是所有帶有key類別的div元素. 注意這個清單本身並不是一個陣列(Array), 只是長得很像. 我們叫它類陣列(Array-Like). 類陣列跟陣列的差別在於, 它少了陣列本身所具有的一些原型屬性(properties) 與方法(methods), 好險Array.forEach()是支援DOM List的!

forEach()方法顧名思義, 能把陣列內的每個元素用自訂的函式迭代執行一次. 舉例如下:

/* 這是 forEach 的舉例 */
var exampleArray = [1, 2, 3];
exampleArray.forEach(number => number += 2); // 結果為 [3, 4, 5]
因此上述程式碼的第二行意思是: 將所有帶有key類別的div元素加上監聽transitionend的監聽器, 只要任何key類別元素轉變完畢, 就執行removeTransition自訂函式, 將放大和發光的效果移除! removeTransition函式的內部長這樣:

function removeTransition(e) {
if (e.propertyName !== ‘transform’) return;
e.target.classList.remove(‘playing’);
}
為什麼會有 if(e.propertyName… 什麼的?
因為CSS轉場的第一個參數是all, 當一個有對應按鈕的按鍵被按下時, 該按鍵的transform, border-color, box-shadow都被新增的 playing 類別影響, 都改變了, 都有轉場, 都會有轉場結束的時候, 因此會觸發好幾個transitionend! 可是我們只需要一個啊!
所以我們只需要留下一個屬性, 並將其它屬性觸發事件的自訂函數返回. 這裡用傳入的事件物件e裡面的propertyName屬性, 獨留transform的回呼函式. 事件物件的target屬性存有觸發事件的HTML元素本身, 在這裡即為剛轉場結束, 帶有key類別的div元素. 利用e.target.classList.remove(‘playing’) 把放大發光效果移除.

整個串起來如下面的code, 好!

function removeTransition(e) {
if (e.propertyName !== ‘transform’) return;
e.target.classList.remove(‘playing’);
}

function playSound(e) {
const audio = document.querySelector(`audio[data-key=”${e.keyCode}”]`);
const key = document.querySelector(`div[data-key=”${e.keyCode}”]`);
if (!audio) return;
key.classList.add(‘playing’);
audio.currentTime = 0;
audio.play();
}

const keys = document.querySelectorAll(‘.key’);
keys.forEach(key =\> key.addEventListener(‘transitionend’, removeTransition));
window.addEventListener(‘keydown’, playSound);

转载请注明:XAMPP中文组官网 » Day 1 – JS Drum Kit

您必须 登录 才能发表评论!