[JavaScript30] 1. Drum Kit | data-*, audio, transitionend
language/javascript

[JavaScript30] 1. Drum Kit | data-*, audio, transitionend

๐Ÿ“ƒ ์š”๊ตฌ์‚ฌํ•ญ

  1. ํ‚ค๋ณด๋“œ๋ฅผ ๋ˆŒ๋ €์„ ๋•Œ, ๋ˆŒ๋ฆฐ ํ‚ค์—์„œ ํ•ด๋‹น ์˜ค๋””์˜ค ์†Œ๋ฆฌ๊ฐ€ ์žฌ์ƒ๋˜๊ฒŒ ํ•œ๋‹ค.
  2. ๋ˆŒ๋ฆฐ ํ‚ค ์š”์†Œ์— playing ํด๋ž˜์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ํŠน์ • css๋ฅผ ์ ์šฉํ•œ๋‹ค.
  3. ๋ชจ๋“  ์ด๋ฒคํŠธ๊ฐ€ ๋๋‚˜๋ฉด, playing ํด๋ž˜์Šค๋ฅผ ์ œ๊ฑฐํ•˜์—ฌ ์ดˆ๊ธฐ ์ƒํƒœ๋กœ ๋ณต๊ตฌํ•œ๋‹ค.

 

โœจ ๊ฒฐ๊ณผํ™”๋ฉด

 

๐Ÿ’ป ์†Œ์Šค์ฝ”๋“œ

document.addEventListener("keydown", playSound); // ํ‚ค ๋ˆŒ๋ ธ์„ ๋•Œ

const keys = document.querySelectorAll(".key"); // ์ด๋ฒคํŠธ ๋๋‚ฌ์„ ๋•Œ
keys.forEach((key) => key.addEventListener("transitionend", removePressed));

// Key๊ฐ€ ๋ˆŒ๋ ธ์„ ๋•Œ ๋™์ž‘ํ•˜๋Š” ํ•จ์ˆ˜
function playSound(e) {
  const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
  const key = document.querySelector(`.key[data-key="${e.keyCode}"]`);

  if (key == null || audio == null) {
    // ๋ˆŒ๋ฆฐ key ๋˜๋Š” audio๊ฐ€ null ์ด๋ฉด ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š๋Š”๋‹ค.
    return;
  }

  audio.currentTime = 0; // ์žฌ์ƒ ์ง€์ ์„ 0์œผ๋กœ ์„ค์ •ํ•˜์—ฌ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค ์žฌ์ƒ ์ง€์  ์ดˆ๊ธฐํ™”
  audio.play();

  key.classList.add("playing");
}

// ์ด๋ฒคํŠธ๊ฐ€ ๋๋‚˜๋ฉด ์‹คํ–‰๋  ํ•จ์ˆ˜
function removePressed(e) {
  if (e.propertyName != "transform") return; // transform์ด ์•„๋‹Œ border, shadow ๋“ฑ์˜ ๊ฒฝ์šฐ์—๋Š” ๋ฌด์‹œ
  // console.log(e.propertyName); // ์ด ๋•Œ, transform์†์„ฑ๋งŒ ์ถœ๋ ฅ๋œ๋‹ค.
  this.classList.remove("playing");
}

 

๐Ÿ“– TIL

data-*

HTML5๋ถ€ํ„ฐ ์ถ”๊ฐ€๋œ ์†์„ฑ์œผ๋กœ, HTML ์š”์†Œ์— ๊ฐ„๋‹จํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค€๋‹ค. data-* ์†์„ฑ์˜ ์ด๋ฆ„ ์ƒ์„ฑ ๊ทœ์น™๊ณผ ๋ฌธ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ๋Œ€์†Œ๋ฌธ์ž ์—ฌ๋ถ€์— ์ƒ๊ด€์—†์ด xml๋กœ ์‹œ์ž‘ํ•˜๋ฉด ์•ˆ๋œ๋‹ค.
  • ์„ธ๋ฏธ์ฝœ๋ก (U+003A)์„ ํฌํ•จํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค.
  • ๋Œ€๋ฌธ์ž๋ฅผ ํฌํ•จํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค.
  • <article
      id="electriccars"
      data-columns="3"
      data-index-number="12314"
      data-parent="cars">
    ...
    </article>

JavaScript์—์„œ data-*์— ์ •์˜๋œ ์†์„ฑ ๊ฐ’๋“ค์„ ์ฝ๋Š” ๋ฐฉ๋ฒ•์€, dataset ์†์„ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ด ๋•Œ, HTML์—์„œ ๋Œ€์‹œ(-)๋กœ ๊ตฌ๋ถ„๋˜์—ˆ๋˜ ์ด๋ฆ„ ๋ฌธ์ž๋Š” camelCase๋กœ ๋ณ€ํ™˜๋œ๋‹ค.

var article = document.getElementById('electriccars');

article.dataset.columns // "3"
article.dataset.indexNumber // "12314"
article.dataset.parent // "cars"

 

Drum Kit ์†Œ์Šค์ฝ”๋“œ์˜ data-* ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ดํŽด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
const key = document.querySelector(`.key[data-key="${e.keyCode}"]`);

์—ฌ๊ธฐ์—์„œ๋Š” ๋ฐฑํ‹ฑ(`)์„ ํ†ตํ•ด ์ ‘๊ทผํ•œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. audio์™€ key๋ฅผ ์ƒ์ˆ˜๋กœ ์„ ์–ธํ•œ ํ›„, audio๋Š” <audio>ํƒœ๊ทธ ์ค‘์—์„œ data-key๊ฐ€ ๋ˆŒ๋ฆฐ ํ‚ค๋ณด๋“œ์˜ KeyCode์ธ ๊ฒƒ์„, key๋Š” ํด๋ž˜์Šค๋ช…์ด key์ธ ๊ฒƒ๋“ค ์ค‘์—์„œ data-key๊ฐ€ ๋ˆŒ๋ฆฐ ํ‚ค๋ณด๋“œ์˜ KeyCode์ธ ๊ฒƒ์„ ๊ฐ€์ ธ์˜จ๋‹ค.

 

audio

HTML5๋ถ€ํ„ฐ ๋„์ž…๋œ <audio> ํƒœ๊ทธ๋Š” ๋ฌธ์„œ์— ์†Œ๋ฆฌ ์ฝ˜ํ…์ธ ๋ฅผ ํฌํ•จํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ <audio src="ํŒŒ์ผ๊ฒฝ๋กœ">๋กœ ์„ ์–ธํ•˜๊ณ , controls, autoplay, loop ๋“ฑ์˜ ๋‹ค์–‘ํ•œ ์†์„ฑ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค. Drum Kit์˜ HTML ์†Œ์Šค์ฝ”๋“œ์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑ๋˜์–ด ์žˆ๋‹ค.

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

JS์—์„œ์˜ ์ ‘๊ทผ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ํ–ˆ๋‹ค.

const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);

audio.currentTime = 0;
audio.play();

audio ์ƒ์ˆ˜๋ฅผ ํ†ตํ•ด querySelector๋กœ ๋ˆŒ๋ฆฐ KeyCode๋ฅผ ๊ฐ€์ง„ <audio> ํƒœ๊ทธ๋กœ ์ ‘๊ทผํ•œ ํ›„, play() ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์Œ์•…์„ ์žฌ์ƒํ•œ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ, ๋‹จ์ˆœํžˆ audio.play(); ๋งŒ ์„ ์–ธ์„ ํ•˜๋ฉด ์ด์ „์— ์žฌ์ƒํ–ˆ๋˜ ์ง€์ ์ด ์ ์šฉ๋˜์–ด ๋ช‡ ์ดˆ๊ฐ„์˜ ํ…€์ด ๋ฐœ์ƒํ•œ๋‹ค. ๊ทธ๋ž˜์„œ currentTime ์†์„ฑ์„ ์ด์šฉํ•˜์—ฌ ์žฌ์ƒ ์ง€์ ์„ ์ดˆ๊ธฐํ™” ํ•ด์ค€๋‹ค.

์†์„ฑ ์„ค๋ช…
autoplay ๋ธŒ๋ผ์šฐ์ €๊ฐ€ load๋  ๋•Œ ์ž๋™ ์žฌ์ƒ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค. boolean ๊ฐ’์„ ๊ฐ–๋Š”๋‹ค.
currentTime ์žฌ์ƒ ์ง€์ ์„ ์„ค์ •ํ•œ๋‹ค. ์ดˆ ๋‹จ์œ„๋กœ, 0์ด๋ฉด ์ฒ˜์Œ๋ถ€ํ„ฐ ์žฌ์ƒํ•œ๋‹ค.
loop ๋ฐ˜๋ณต ์žฌ์ƒ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค. boolean ๊ฐ’์„ ๊ฐ–๋Š”๋‹ค.
src audio์˜ ๊ฒฝ๋กœ(URL)๋ฅผ ์„ค์ •ํ•œ๋‹ค.
volume ์Œ๋Ÿ‰์„ ์ง€์ •ํ•œ๋‹ค. 0.0~1.0 ์‚ฌ์ด์˜ ๊ฐ’์œผ๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

transitionend

transitionend๋Š” CSS์˜ transition์ด ์™„๋ฃŒ๋œ ์ดํ›„์— ๋ฐœ์ƒํ•˜๋Š” ์ด๋ฒคํŠธ๋กœ, transition๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜๋Š” ํ•จ์ˆ˜์ด๋‹ค. 

const transition = document.querySelector('.transition');

transition.addEventListener('transitionend', () => {
  console.log('Transition ended');
});

์œ„์™€ ๊ฐ™์ด addEventListener๋ฅผ ํ†ตํ•ด transitionend์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. Drum Kit์—์„œ๋Š” ํ‚ค๊ฐ€ ๋ˆŒ๋ ธ์„ ๋•Œ key.classList.add("playing")์„ ํ†ตํ•ด ํ•ด๋‹น ์š”์†Œ์— playing ํด๋ž˜์Šค๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค. ๊ทธ๋Ÿฐ๋ฐ transitionend๋ฅผ ํ•˜์ง€ ์•Š์œผ๋ฉด, ํ‚ค๊ฐ€ ๋ˆŒ๋ฆฐ ํšจ๊ณผ๊ฐ€ ์ง€์†๋œ๋‹ค. ๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ํ•ด๋‹น ์ด๋ฒคํŠธ๊ฐ€ ๋๋‚˜๋ฉด, playing ํด๋ž˜์Šค๋ฅผ remove ํ•ด์„œ ์ƒํƒœ๋ฅผ ์›์ƒ๋ณต๊ตฌ ์‹œ์ผœ์ค˜์•ผ ํ•œ๋‹ค. Drum Kit ์†Œ์Šค์ฝ”๋“œ์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ–ˆ๋‹ค.

const keys = document.querySelectorAll(".key");
keys.forEach((key) => key.addEventListener("transitionend", removePressed));

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

keys ์ƒ์ˆ˜๋ฅผ ์„ ์–ธํ•˜์—ฌ key ํด๋ž˜์Šค๋ฅผ ๊ฐ€์ง„ ๋ชจ๋“  ์š”์†Œ๋“ค์„ ๊ฐ€์ ธ์˜จ๋‹ค. querySelectorAll()์„ ์ด์šฉํ•ด์„œ ๊ฐ€์ ธ์™”๋Š”๋ฐ, querySelectorAll()์€ ์กฐ๊ฑด์— ๋งž๋Š” ๋ชจ๋“  ์š”์†Œ๋“ค์„ NodeList๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ keys.addEventListener์™€ ๊ฐ™์ด ์„ ์–ธํ•˜๋ฉด ๋ชจ๋“  ์š”์†Œ๋“ค์— ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†๋‹ค. ๊ทธ๋ž˜์„œ forEach๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ชจ๋“  ์š”์†Œ๋“ค์„ ์ˆœํšŒํ•˜๋ฉฐ ๊ฐ๊ฐ์˜ ์š”์†Œ๋“ค์—  ์ด๋ฒคํŠธ๋ฅผ ๊ฑธ์–ด์ค˜์•ผ ํ•œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์‹คํ–‰ ์‹œ๊ฐ„์ด ๊ฐ€์žฅ ๊ธด transform๋งŒ ๋ฝ‘์•„์„œ ์–˜๊ฐ€ ๋๋‚˜๋ฉด ๋ชจ๋“  transition ์š”์†Œ๋“ค์ด ์ž๋™์œผ๋กœ ๋๋‚  ์ˆ˜ ์žˆ๋„๋ก ํ•ด ์ฃผ๋ฉด ๋œ๋‹ค. propertyName์†์„ฑ์„ ํ™œ์šฉํ•ด์„œ transform์„ ๋ฝ‘์•„๋‚ธ ํ›„, ํ•ด๋‹น ์ด๋ฒคํŠธ๊ฐ€ ๋๋‚˜๋ฉด classList.remove("playing")์„ ํ†ตํ•ด playing ํด๋ž˜์Šค๋ฅผ ์ œ๊ฑฐํ•˜์—ฌ CSS ์š”์†Œ๋ฅผ ์ฒ˜์Œ์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•˜๋ฉด ๋œ๋‹ค.

(์˜์ƒ์—์„œ "this"๋Š” always equal to the whatever got called against it. ์ด๋ผ๊ณ  ํ•œ๋‹ค. ๋‚˜์ค‘์— this์— ๋Œ€ํ•ด์„œ๋„ ํ•œ ๋ฒˆ ๊ณต๋ถ€ํ•˜๊ณ  ์ •๋ฆฌํ•ด์•ผ๊ฒ ๋‹ค.)

 

 

 

๐Ÿ‘€ ์ฐธ๊ณ 

youtube Wes Bos

https://www.youtube.com/watch?v=VuN8qwZoego&list=PLu8EoSxDXHP6CGK4YVJhL_VWetA865GOH 

 

https://developer.mozilla.org/ko/docs/Learn/HTML/Howto/Use_data_attributes

 

https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/transitionend_event