第一天挑战(架子鼓)
所有内容均上传至gitee,答案不唯一,仅代表本人思路
本人gitee:gitee.com/thats-all-r…
效果
-
样式
- 大组件页面内居中,小组件平均排布
- 触发事件后,外侧有黄色边框及阴影
-
逻辑分析
- 键盘按下对应的按键时,添加样式,并播放对应音频
- 键盘长按会多次触发该效果
- 键盘松开效果消失
本人代码及思路分析
仅提供布局及逻辑代码
结构:
<body>
<div class="main">
<div class="key">
<div>
<P>A</P>
<P>CLAP</P>
<audio src="sounds/clap.wav"></audio>
</div>
<div>
<P>S</P>
<P>HHAT</P>
<audio src="sounds/hihat.wav"></audio>
</div>
<div>
<P>D</P>
<P>KICK</P>
<audio src="sounds/kick.wav"></audio>
</div>
<div>
<P>F</P>
<P>OPENHAT</P>
<audio src="sounds/openhat.wav"></audio>
</div>
<div>
<P>G</P>
<P>BOOM</P>
<audio src="sounds/boom.wav"></audio>
</div>
<div>
<P>H</P>
<P>RIDE</P>
<audio src="sounds/ride.wav"></audio>
</div>
<div>
<P>J</P>
<P>SNARE</P>
<audio src="sounds/snare.wav"></audio>
</div>
<div>
<P>K</P>
<P>TOM</P>
<audio src="sounds/tom.wav"></audio>
</div>
<div>
<P>L</P>
<P>TINK</P>
<audio src="sounds/tink.wav"></audio>
</div>
</div>
</div>
逻辑:
let itemList = []
const key = document.querySelectorAll('.key div p')
const keyList = Array.from(key)
function isValue(e, item) {
if (e.key === item.innerHTML || e.key.toUpperCase() === item.innerHTML) {
return true
}
}
document.addEventListener('keydown', (e) => {
keyList.forEach((item, index) => {
if (isValue(e, item)) {
if (itemList.indexOf(item) === -1) {
itemList.push(item)
}
item.parentNode.classList.add('red')
item.nextElementSibling.nextElementSibling.currentTime = 0
item.nextElementSibling.nextElementSibling.play()
}
})
})
document.addEventListener('keyup', (e) => {
itemList.forEach((item) => {
if (isValue(e, item)) {
item.parentNode.classList.remove('red')
}
})
})
分析:
-
整体思路: 首先获取p标签内代表键盘的值,为全局注册点击事件,根据键盘按下的值和对应标签内的值进行对比,如果一致则为其添加触发效果,松开键盘后删除样式
-
具体实现:
- 首先通querySelectorAll选取所有的p标签,并把其转换为数组对象(默认获取的是一个NodeList,不具备数组方法)
- 其次注册键盘事件,因为要按下时要持续触发,所以注册的是keydown事件
- 注册事件后,通过forEach判断数组内的元素和键盘按下的值是否相等
- 封装isValue判断函数,减少代码冗余
- 如果相等则将其添加到itemList(等待删除队列) 中,通过parentNode获取其父元素节点添加样式
- 根据html结构,通过两次nextElementSibling获取到对应的audio标签,并进行播放
- 最后当对应的键盘按键抬起时,将itemList中对应的元素样式删除
-
弊端分析:
- 多次使用forEach和数组,影响性能开销
- 多次对节点进行操作,特别是两次nextElementSibling,对于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-key="68" class="key">
<kbd>D</kbd>
<span class="sound">kick</span>
</div>
<div data-key="70" class="key">
<kbd>F</kbd>
<span class="sound">openhat</span>
</div>
<div data-key="71" class="key">
<kbd>G</kbd>
<span class="sound">boom</span>
</div>
<div data-key="72" class="key">
<kbd>H</kbd>
<span class="sound">ride</span>
</div>
<div data-key="74" class="key">
<kbd>J</kbd>
<span class="sound">snare</span>
</div>
<div data-key="75" class="key">
<kbd>K</kbd>
<span class="sound">tom</span>
</div>
<div data-key="76" class="key">
<kbd>L</kbd>
<span class="sound">tink</span>
</div>
</div>
<audio data-key="65" src="sounds/clap.wav"></audio>
<audio data-key="83" src="sounds/hihat.wav"></audio>
<audio data-key="68" src="sounds/kick.wav"></audio>
<audio data-key="70" src="sounds/openhat.wav"></audio>
<audio data-key="71" src="sounds/boom.wav"></audio>
<audio data-key="72" src="sounds/ride.wav"></audio>
<audio data-key="74" src="sounds/snare.wav"></audio>
<audio data-key="75" src="sounds/tom.wav"></audio>
<audio data-key="76" src="sounds/tink.wav"></audio>
逻辑
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 = Array.from(document.querySelectorAll('.key'));
keys.forEach(key => key.addEventListener('transitionend', removeTransition));
window.addEventListener('keydown', playSound);
分析
仅代表本人对该代码的分析
-
整体思路: 从结构上看,给每个案件元素和对应的音频都添加了自定义属性进行绑定,通过触发keydown为对应的元素添加样式,为每一个可能被触发的元素绑定事件,通过监听transitionend,如果样式添加完毕,则删除样式
-
具体实现:
- removeTransition函数: 监听transitionend事件的propertyName,并且在添加的样式中使用transfrom属性,如果propertyName的值为transfrom,代表添加的样式已经触发,则把该样式删除掉
- playSound函数: 根据键盘触发事件对应的keycode来选定与之对应的元素,并为其添加样式,开启音频
- 选中所有需要被触发的元素,将其转换成数组,并为每一个都绑定transitionend事件进行监听
- 绑定键盘事件
-
优点:
- 通过自定义属性对元素进行选取,每次按下键盘就可以选取对应的元素,避免了大量选用元素造成的问题
- 自定义属性的方法避免使用nextElementSibling,提高了维护性,即使html结构进行更改也不会受到影响
- 通过监听transitionend事件让元素在添加完成后就可以立即进行销毁,十分高效
- 根据键盘对应值进行绑定的方法, 避免了大小写转换的问题
-
transitionend(详细内容请访问MDN进行查询)
- 在某个 CSS transition 完成时触发。transitionend会提供一个属性叫propertyName
-
propertyName
- 该值会记录在transition后哪些css样式发生了改变,每有一个值改变,就会触发一次监听,propetryName也会随之改变