30天JS挑战(第一天)

122 阅读3分钟

第一天挑战(架子鼓)

地址:javascript30.com/

所有内容均上传至gitee,答案不唯一,仅代表本人思路

本人gitee:gitee.com/thats-all-r…

效果

image.png

  • 样式

    • 大组件页面内居中,小组件平均排布
    • 触发事件后,外侧有黄色边框及阴影
  • 逻辑分析

    • 键盘按下对应的按键时,添加样式,并播放对应音频
    • 键盘长按会多次触发该效果
    • 键盘松开效果消失

本人代码及思路分析

仅提供布局及逻辑代码

结构:

<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也会随之改变