| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
- api 라우트
- jQuery
- 힘들었던 한주
- AJAX
- 서버 컴포넌트
- git flow finish
- 책으론 원리만
- git flow start
- CSS
- 공부할 거 넘많다~
- JavaScript
- 바닐라JS
- freecodecamp
- 다시 홧팅
- git
- 끝까지 잘 마무리하기
- 백준
- Main
- 클라이언트 컴포넌트
- 실무는 공식문서로
- TS
- HTML
- 차이점
- javascript30
- 개발일지
- Next.js
- fetch pull 차이
- axios
- js
- Mac
- Today
- Total
다다의 개발일지 6v6
[JS] Day 1 - Javascript Drum Kit ( 키보드로 드럼치기!! 바닐라 js 프로젝트 1 시작 ) 본문
[JS] Day 1 - Javascript Drum Kit ( 키보드로 드럼치기!! 바닐라 js 프로젝트 1 시작 )
dev6v6 2025. 2. 19. 01:16
JavaScript 30
Build 30 things with vanilla JS in 30 days with 30 tutorials
javascript30.com
우선 DOM조작, 이벤트 핸들링 부터 알아보자
DOM 조작을 위한 주요 document 메서드
특정 요소를 선택하는 방법
document.getElementById("id이름"); // ID로 요소 선택
document.getElementsByClassName("클래스이름"); // 클래스 이름으로 여러 개 선택 (배열 비슷한 형태)
document.getElementsByTagName("태그이름"); // 태그 이름으로 여러 개 선택 (예: "div", "p")
document.querySelector("선택자"); // CSS 선택자 방식 (가장 처음 나오는 요소 선택)
document.querySelectorAll("선택자"); // CSS 선택자로 모든 요소 선택 (NodeList 반환)
둘 다 id="item"인 요소를 선택
document.getElementById("item"); // id가 "item"인 요소 선택
document.querySelector("#item"); // CSS 선택자로 id가 "item"인 요소 선택
차이점 정리
| getElementById("id") | ID로 요소 선택 | 단일 요소 (HTMLElement) | 빠름 🚀 |
| querySelector("#id") | CSS 선택자로 요소 선택 | 단일 요소 (Element) | 약간 느림 |
- ID 선택만 할 거면 getElementById()가 직관적이고 빠름.
- CSS 선택자(클래스, 태그 등)를 같이 쓰고 싶다면 querySelector()가 유용.
큰 프로젝트라면 성능 때문에 getElementById 이걸 쓰는게 낫다고 함
새로운 요소 만들기
document.createElement("태그이름"); // 새로운 요소 생성
✅ 사용 예시
let newDiv = document.createElement("div");
newDiv.textContent = "새로운 div 요소!";
document.body.appendChild(newDiv); // body 태그 안에 추가
DOM에서 요소를 제거하는 방법
요소.remove(); // 직접 삭제
부모요소.removeChild(자식요소); // 부모 기준으로 삭제
✅ 사용 예시
let box = document.getElementById("box");
box.remove(); // box 요소 삭제
속성 및 내용 변경
요소.textContent = "텍스트 변경"; // 순수 텍스트 변경
요소.innerHTML = "<b>HTML 변경</b>"; // HTML 포함 변경
요소.setAttribute("속성이름", "값"); // 속성 추가/변경
요소.getAttribute("속성이름"); // 속성 값 가져오기
요소.removeAttribute("속성이름"); // 속성 제거
✅ 사용 예시
let title = document.getElementById("myTitle");
title.textContent = "새로운 제목";
title.setAttribute("class", "highlight");
이벤트 핸들링 (사용자 입력 감지)
이벤트 추가하는 방법
요소.addEventListener("이벤트명", 함수);
✅ 사용 예시
let btn = document.getElementById("myButton");
btn.addEventListener("click", function () {
alert("버튼 클릭됨!");
});
자주 쓰이는 이벤트 종류
| "click" | 클릭 시 실행 |
| "mouseover" | 마우스를 요소 위에 올릴 때 |
| "mouseout" | 마우스가 요소 밖으로 나갈 때 |
| "keydown" | 키보드를 누를 때 |
| "keyup" | 키보드를 뗄 때 |
| "change" | <input> 값이 변경될 때 |
| "submit" | 폼이 제출될 때 |
✅ 키보드 이벤트 예제
document.addEventListener("keydown", function (event) {
console.log("눌린 키: " + event.key);
});
1. 먼저 키를 눌렀을 때 소리 나게 만들어 보자! + 애니메이션
keydown 이벤트에서 event 객체에 들어있는 정보
이 객체에는 어떤 키가 눌렸는지, 어떤 요소에서 발생했는지 같은 정보를 포함하고 있다.
이벤트 객체(e)에서 자주 쓰는 속성
A 키 입력 시
| e.key | 사용자가 입력한 키 값 | "a" (소문자) |
| e.code | 물리적 키 이름 | "KeyA" |
| e.keyCode | 키 코드(숫자, 옛날 방식) | 65 |
| e.shiftKey | Shift 키가 눌렸는지 | true 또는 false |
| e.ctrlKey | Ctrl 키가 눌렸는지 | true 또는 false |
| e.altKey | Alt 키가 눌렸는지 | true 또는 false |
| e.repeat | 키가 계속 눌려있는지 | true 또는 false |
일단 키를 눌렀을 때 소리 나게 만들어 보자! + 애니메이션
document.addEventListener("keydown", function (e) {
// key를 눌렀을 때 그 키의 data-key와 같은 .key요소 찾기
let key = document.querySelector(`.key[data-key="${e.keyCode}"]`);
let audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
if (!key || !audio) return; // 해당하는 키가 없으면 종료 (우리가 설정한 키 이외의 키면 종료)
key.classList.add("playing"); // 키에 애니메이션 효과 추가
audio.currentTime = 0; // 소리를 처음부터 재생 (연타 가능)
audio.play();
});
이거를 좀 더 정리해서 따로 함수로 만들어 주면
function playSound(e) {
// key를 눌렀을 때 그 키의 data-key와 같은 .key요소 찾기
let key = document.querySelector(`.key[data-key="${e.keyCode}"]`);
let audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
if (!key || !audio) return; // 해당하는 키가 없으면 종료 (우리가 설정한 키 이외의 키면 종료)
key.classList.add("playing"); // 키에 애니메이션 효과 추가
audio.currentTime = 0; // 소리를 처음부터 재생 (연타 가능)
audio.play();
}
window.addEventListener("keydown", playSound);
여기서 document가 아닌 window를 이용해서 이벤트 핸들링한 이유
window.addEventListener("keydown", playSound);
- window → 브라우저 창 전체를 의미.
- .addEventListener("keydown", playSound) → 키보드를 누르면(keydown) playSound 함수를 실행.
- 즉, 사용자가 어디에 포커스가 있든 키를 누르면 작동함.
window와 document 두 객체의 차이점
| window | document | |
| 대상 | 브라우저 창 | 웹 페이지(HTML) |
| 역할 | 브라우저 기능 조작 | HTML 조작 |
| 주요 기능 | 창 크기, 스크롤, 알림창 | 요소 선택, 내용 변경 |
즉, document는 window 안에 있는 HTML 문서를 다루는 객체
document.addEventListener("keydown", playSound); 해도 되지 않을까?
document에서도 이벤트를 감지할 수 있지만 HTML 문서 내에서만 감지하고
window에서 감지하는 이유는 "웹 페이지의 어디에서든 키 입력을 받을 수 있도록 하기 위해서"이다.
차이점 window.addEventListener document.addEventListener
| 감지 범위 | 브라우저 창 전체 | HTML 문서 내에서만 |
| 포커스 필요 여부 | 없음 (어디서든 가능) | 웹 페이지에 포커스가 있어야 가능 |
정리하자면
- window.addEventListener("keydown", playSound);
→ 브라우저 창에서 키보드 입력을 감지해서 playSound 실행! - window를 사용하면 어디에서든(심지어 입력창에 포커스가 있어도) 키 입력을 놓치지 않음.
- document.addEventListener("keydown", playSound);도 가능하지만, window가 더 범용적으로 작동함.
여기까지 하면 눌렀을 때 소리는 나지만 소리가 끝난 후에도 애니메이션이 적용되어 있다.

2. 이제 애니메이션이 끝났을 때 효과가 사라지도록 해보자
애니메이션이 끝났음을 감지하는 transitionend 이벤트 이용할 거임!
transitionend 이벤트란?
CSS transition(변화)이 끝났을 때 실행되는 이벤트
예를 들어, 버튼이 커지는 애니메이션이 끝났을 때 실행하고 싶은 코드가 있다면 transitionend를 사용할 수 있다.
element.addEventListener("transitionend", function (event) {
console.log("애니메이션 끝남!", event.propertyName);
});
✅ 특정 요소(element)에서 CSS transition이 끝나면 이벤트 실행!
✅ event.propertyName → 어떤 속성의 변화가 끝났는지 확인 가능. = 변화가 끝난 속성의 이름을 띄운다
이 프로젝트에서는 크기 변화라 속성 이름이 transform이다
function removeTransition(e) {
if (e.propertyName !== 'transform') return; // transform이 아닌 다른 속성의 변화가 끝나면 패스!!
e.target.classList.remove('playing'); // 애니메이션 끝나면 원래 상태로 복구
}
- 키를 누르면 (keydown) playing 클래스 추가 → CSS에서 애니메이션 효과 적용.
- 애니메이션(transform)이 끝나면 "transitionend" 이벤트 발생!
- removeTransition()이 실행돼서 propertyName 확인 후 transform이 맞으면
- playing 클래스 제거 → 원래 상태로 복구.
transitionend가 필요한 이유
만약 transitionend를 사용하지 않고 직접 setTimeout을 써서 클래스를 제거하면?
// 비효율적인 코드
document.addEventListener("keydown", function (e) {
const key = document.querySelector(`div[data-key="${e.keyCode}"]`);
if (!key) return;
key.classList.add("playing");
setTimeout(() => {
key.classList.remove("playing"); // 일정 시간 후 제거
}, 200); // transition 시간과 맞춰야 함
});
- setTimeout(200)처럼 CSS transition 시간과 정확히 맞춰야 해서 유지보수가 어려움.
- 애니메이션 속도를 변경하면 setTimeout 값도 수정해야 함.
- transitionend를 사용하면 자동으로 애니메이션이 끝나는 타이밍에 실행되므로 더 효율적이다 ^^
const keys = document.querySelectorAll('.key');
keys.forEach(key => key.addEventListener('transitionend', removeTransition));
- document.querySelectorAll('.key')로 모든 .key 요소를 선택해 keys에 저장.
- keys.forEach()로 각 key 요소에 대해 transitionend 이벤트를 감지하고, 애니메이션이 끝났을 때마다 removeTransition 함수가 실행되도록 설정.
- removeTransition 함수에서는 애니메이션이 끝난 속성이 transform일 때만 'playing' 클래스를 제거.
키가 여러번 눌려도 transitionend 이벤트가 계속 발생하는 이유
keys.forEach(key => key.addEventListener('transitionend', removeTransition));
이렇게 이벤트 리스너를 추가하면, transitionend 이벤트가 발생할 때마다 removeTransition 함수가 계속 호출된다. 즉, 재사용이 가능!
왜 계속 재사용이 가능할까?
이벤트 리스너는 "이벤트가 발생할 때마다" 그에 해당하는 콜백 함수를 실행하도록 설정되기 때문에, 한 번의 이벤트 처리 후에도 리스너는 계속 활성화된 상태에 있음.
이벤트 리스너의 동작
- transitionend 이벤트가 한 번 발생하면 removeTransition 함수가 실행.
- transitionend 이벤트가 다시 발생하면, 그때마다 removeTransition 함수가 새롭게 실행.
- 이벤트 리스너는 한번 추가되면 계속 동작하므로, 매번 새로운 이벤트 발생마다 자동으로 실행된다!
결과물!!! ㅜㅜ
script
function playSound(e) {
// key를 눌렀을 때 그 키의 data-key와 같은 .key요소 찾기
let key = document.querySelector(`.key[data-key="${e.keyCode}"]`);
let audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
if (!key || !audio) return; // 해당하는 키가 없으면 종료 (우리가 설정한 키 이외의 키면 종료)
key.classList.add("playing"); // 키에 애니메이션 효과 추가
audio.currentTime = 0; // 소리를 처음부터 재생 (연타 가능)
audio.play();
}
function removeTransition(e) {
if (e.propertyName !== "transform") return; // transform이 아닌 다른 속성의 변화가 끝나면 패스!!
e.target.classList.remove("playing"); // 애니메이션 끝나면 원래 상태로 복구
}
window.addEventListener("keydown", playSound);
const keys = document.querySelectorAll(".key");
keys.forEach((key) =>
key.addEventListener("transitionend", removeTransition)
);
'Frontend > JavaScript' 카테고리의 다른 글
| Ajax - JS에서 클라이언트와 서버의 '비동기적' API 통신 방식 + jQuery, fetch API, Axios, SSE, WebSockets (0) | 2025.02.24 |
|---|---|
| [JS] 함수 호이스팅에 대해서도 알아보자. + 함수 선언문, 함수 표현식, 화살표 함수, 함수에서의 this (2) | 2025.02.21 |
| [JS] var, let, const의 차이점 + 변수호이스팅 , 스코프 (0) | 2025.02.21 |
| [JS] Day 3 - CSS Variables ( JS로 CSS 변수 수정하기! ) (0) | 2025.02.21 |
| [JS] day2 - JS and CSS Clock ( 돌아가는 시계 만들기 ) (0) | 2025.02.19 |