JavaScript30 为Wes Bos推出的一项为期30天的挑战,旨在帮助人们用纯JavaScript来实现效果,初学者若想在JS方面快速精进,不妨一试。现在你看到的是该系列总结的第一篇,不知何时能做完30题,就不在此信誓旦旦立flag了。
我的项目地址是 js 1/30。
实现效果
利用JS实现模拟打鼓的效果,敲击键盘字母(A-L),即可播放对应声音,同时当前字母伴随敲击声效出现动画。

查看 demo。
解题思路
- 监听键盘事件
window.addEventListener('keydown', playaudio);
- 创建function playaudio()
- 利用当前e.keyCode来获取对应键码的div标签和audio标签。
- 判断对应的audio是否存在,若是则播放该音频,并添加按钮样式。
- 添加transitionend事件,移除样式,恢复原状。
初始代码
页面基础布局
<div class="keys">
<div data-key="65" class="key">
<kbd>A</kbd>
<span class="sound">clap</span>
</div>
······
<!-- B-K部分代码省略,请参阅代码 -->
<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="76" src="sounds/tink.wav"></audio>
CSS代码
html {
font-size: 10px;
background: url(http://i.imgur.com/b9r5sEL.jpg) bottom center;
background-size: cover;
/* 把背景图像扩展至足够大,以使背景图像完全覆盖背景区域。 */
}
body,html {
margin: 0;
padding: 0;
font-family: sans-serif;
}
.keys {
display: flex;
flex: 1;
min-height: 100vh;
/* 设置段落的最小高度,其中vh是可视区百分比高度单位,如1vh等于可视区高度的百分之一 */
align-items: center;
/* 弹性盒子元素在该行的侧轴(纵轴)上居中放置。 */
justify-content: center;
/* 弹性盒子元素将向行中间位置对齐。该行的子元素将相互对齐并在行中居中对齐,
同时第一个元素与行的主起始位置的边距等同与最后一个元素与行的主结束位置的边距 */
}
.key {
border: .4rem solid black;
border-radius: .5rem;
/* 向 div 元素添加圆角边框 */
margin: 1rem;
font-size: 1.5rem;
padding: 1rem .5rem;
transition: all .07s linear;
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-color: #ffc600;
box-shadow: 0 0 1rem #ffc600;
}
/* 该样式将在后文反复提及 */
kbd {
display: block;
font-size: 4rem;
}
.sound {
font-size: 1.2rem;
text-transform: uppercase;
letter-spacing: .1rem;
color: #ffc600;
}
解题难点
1.如何将键盘A-L按键与对应标签及音频联系起来?
在初始代码中,我们可以看到div.key和audio都传入了一个data-key,可以此为突破口,只需找到与e.keyCode等值的标签即可。
const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
const key = document.querySelector(`div[data-key="${e.keyCode}"]`);
通过此网站可快速查询keyCode.
2.当持续按住按键,如何使得音频马上相应?
设置audio播放时间为0
audio.currentTime = 0;
3.样式生效后,如何使得页面标签恢复原状?
添加transitionend事件,移除样式。 transitionend 事件会在 CSS transition 结束后触发。
4.长时间按住某个按键之后,会发现一个bug,div.key动画挥之不去。
同样添加键盘监听事件,利用keyup移除效果。
window.addEventListener('keyup',removeT);
JavaScript完整代码如下:
<script>
function removeT(e) {
if (e.propertyName !== 'transform') return;
e.target.classList.remove('playing');
}
function playaudio(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', removeT));
window.addEventListener('keydown', playaudio);
window.addEventListener('keyup', removeT);
注意事项
- 区分大小写: propertyName,keyCode等等,这道题我做了两遍,两次都摔在了这个坑里,引以为戒。
- querySelectorAll: 返回与指定的选择器组匹配的文档中的元素列表 ,需要注意的是返回对象为 NodeList ,而不是Array,也可以用Array.from()将其转换为数列,当然即使不转换,也可以使用forEach函数遍历。
- forEach函数:较for循环更为简洁,示例如下:
var arr = [1, 2, 3];
for(var i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
等同于,
var arr = [1, 2, 3];
arr.forEach((val) => {
console.log(arr[i]);
})
也可用用reduce、map、filter这样的函式代替,详见该网站。
反引号``包裹字符串,常与${变量}连用。 普通字符串:
var a = 5;
var b = 10;
console.log("Fifteen is " + (a + b) + " and\nnot " + (2 * a + b) + ".");
// "Fifteen is 15 and
// not 20."
模板字符串:
var a = 5;
var b = 10;
console.log(`Fifteen is ${a + b} and\nnot ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."
另,在回顾总结中,发现liyuechun的博客,受益良多,记录一笔。