상세 컨텐츠

본문 제목

[Javascript] 01. Drum Kit

Experience/[Javascript] JS 30

by winCow 2021. 4. 12. 13:06

본문

1. 배경

클론 코딩 학습의 일환이다.

HTML, CSS가 완성되어 있는 프로젝트에 javascript로 기능을 추가하려고 한다.

추가할 기능은 키보드의 특정 버튼을 누르면 해당 키보드에 부여한 사운드가 출력되고, 일시적으로 해당 버튼의 테두리를 테두리를 노랗게 바꾸는 애니메이션 효과가 나오는 것이다.

 

 

2. HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>JS Drum Kit</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>


  <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>

<script>
</script>


</body>
</html>

HTML은 위와 같이 입력되어 있다. 코드가 간단하므로 js 파일을 따로 만들지 않고, 하단의 script 태그에 입력하고자 한다. 위 HTML 코드에서 dataset 속성으로 각각 숫자가 주어진 것을 확인할 수 있는데, 이는 자바스크립트의 이벤트 키 코드로, 각각의 키에 대하여 고유한 값을 가지고 있다.

keycode.info/

 

JavaScript Event KeyCodes

Press any key to get the JavaScript event keycode

keycode.info

위 페이지에서 각각의 키에 대한 키 코드를 확인할 수 있다. 

 

 

3. <script> main system1

  window.addEventListener('keydown', playSound);
 //window.addEventListener('keydown', e => playSound(e)); 도 정상적으로 실행된다.

window 객체는 전역객체로, 브라우저의 요소들과 자바스크립트 엔진, 모든 요소를 가지고 있는 모든 객체의 조상이다. 그러므로 window.addEventListener를 사용한 최상단의 코드는, 브라우저의 모든 요소에 대해 keydown 이벤트가 발생하면 playSound 함수를 실행한다는 의미이다. 

 

 

 

4. <script> playSound

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

이 때 키 코드를 포함하여, 눌러진 키에 대한 정보가 playSound 함수로 전달된다. 전달된 정보를 매개변수 e라고 하여 playSound 함수를 정의하면 위와 같다.

 

※ 이벤트 리스너의 콜백함수는 자동적으로 첫 번째 인자로 이벤트 객체를 받는다. 따라서 위 이벤트 리스너에서 playSound라는 함수만을 전달하고, 매개변수를 전달하지 않아도 정상적으로 실행된다. 함수 뒤의 괄호()는 실행을 의미하는 명령어이므로, 콜백함수를 전달할 때 playSound()는 undefined를 반환한다.

 

Attribute Selector, 특성 선택자는 [attribute] 형태로 해당 속성을 가진 요소를 선택하거나, [attribute = value] 형태로 해당 속성 값이 value인 요소를 선택하는 등의 기능을 제공한다. playSound 함수의 변수 audio는 data-key 값이 전달된 키보드의 정보의 keyCode 값과 일치하는 오디오 요소를 선택하고, 변수 key는 마찬가지로 data-key 값이 전달된 키보드의 정보의 keyCode 값과 일치하는 .key 클래스의 div 요소를 선택한다.

브라우저의 모든 요소에 대하여 addEventListener를 적용하였으나, audio에 해당하지 않는 요소와는 무관하므로 audio가 아닌 요소는 아무것도 반환하지 않도록 한 뒤, audio.play()를 통해 사운드를 실행시키도록 했다. 또, 이 상태로 실행시키는 경우, 해당 키보드를 누르면 소리가 재생되지만, 같은 키보드를 반복해서 눌러도 해당 사운드가 끝날 때까지 다음 사운드는 실행되지 않는다. 그러므로 실제로 드럼을 치는 것처럼 연속적으로 소리를 내기 위해서는 audio.currentTime = 0으로 설정해 주어야 한다. 마지막으로, 키를 누를 때, 해당하는 div의 윤곽선이 순간적으로 노랗게 변하도록 설정하기 위해 해당하는 키에 playing 클래스를 추가하였다.

 

 

5. <script> main system2

  const keys = document.querySelectorAll('.key');
  keys.forEach(key => key.addEventListener('transitionend', removeTransition));

querySelectorAll 매소드는 HTML 문서에서 조건에 부합하는 모든 요소들을 Nodelist로 반환한다. Nodelist는 배열이 아니지만 forEach() 매소드를 이용하여 반복할 수 있고, Array.from() 매소드를 통해 배열로 변환할 수 있다. 위 코드에서는 key라는 클래스를 가지는 모든 HTML 요소의 Nodelist를 keys라고 정의하였다. 이를 forEach를 통해 반복하는데, 각각의 key, 즉 key 클래스를 가지는 각각의 HTMl 요소에 대하여 addEventListener로 트랜지션이 끝났을 때 removeTransition 함수를 실행하도록 하였다. transitionend 이벤트는 CSS의 트랜지션이 끝났을 때 실행된다. 

 

 

6. <script> removeTransition

 function removeTransition(e) {
    if(e.propertyName !== 'transform') return;
    this.classList.remove('playing');
  }

주어진 CSS 파일을 보면, transition: all .07s ease라는 트랜지션이 존재한다. CSS 트랜지션은 CSS의 속성을 변경할 때, 즉, 위에서 정의한 playing 클래스가 추가될 때, 애니메이션 속도를 조절할 수 있게 한다. 즉, 이 코드로 인해 0.7초에 걸쳐 트랜지션이 발생하고, 이러한 트랜지션이 종료되면, 위의 main system2에 의해 removeTransition 함수가 실행된다. 트랜지션 종료 이벤트가 매개변수로 전달되며 종료된 이벤트의 propertyName이 transform이 아닌 경우에는 아무것도 반환하지 않고 playing 클래스를 제거한다. propertyName은 트랜지션을 완료한 CSS 속성의 이름을 나타내며, 콘솔 창에서 확인할 수 있다.

 

관련글 더보기

댓글 영역