const parentEl = document.querySelector('.parent');
const childEl = document.querySelector('.child');
const handler = () => {
console.log('Parent!');
}
parentEl.addEventListener('click', handler);
childEl.addEventListener('click', () => {
parentEl.removeEventListener('click', handler);
console.log('Child!');
});
const parentEl = document.querySelector('.parent');
parentEl.addEventListener('click', event => {
console.log(event.target, event.currentTarget);
console.log(event); //event 객체의 모든 내용 (속성과 값)을 확인할 수 있음
})
//wheel 이벤트는 스크롤을 움직이면 (= 마우스의 휠을 움직이면) 발생하는 이벤트
parentEl.addEventListener('wheel', event => {
console.log(event);
})
event.target
: 이벤트가 발생한 해당 요소
event.currentTarget
: 이벤트가 등록된 요소
event
라는 매개변수로 들어가고 그것을 이벤트 객체라고 부른다. 그리고 그 이벤트 객체에서 target 이나 currentTarget 같은 속성을 사용할 수 있다.const inputEl = document.querySelector('input');
inputEl.addEventListener('keydown', event => {
console.log(event.key);
})
event.preventDefault()
: 해당 이벤트가 발생하는 것을 막는 것은 아니고, 브라우저가 가지고 있는 기본적인 동작만 방지하는 것
const parentEl = document.querySelector('.parent');
parentEl.addEventListener('wheel', event => {
event.preventDefault();
console.log('Wheel!');
})
const anchorEl = document.querySelector('a');
anchorEl.addEventListener('click', event => {
event.preventDefault();
console.log('click!');
})
(참조) 버블링과 캡처링
이벤트 버블링
: 어떤 요소의 부모, 조상 요소들에 핸들러가 할당되어 있을 때, 해당 자식 요소의 이벤트가 발생하면 부모, 조상을 타고 올라가면서 각 요소에 할당된 핸들러가 계속 동작하는 현상
event.stopPropagation()
: 버블링 정지
해당 코드를 입력한 이벤트까지만 실행되고 그 위로(조상)들은 버블링 정지된다.
이벤트 캡처링
: 캡처 옵션을 추가해서 상위 요소의 이벤트가 먼저 동작하게 하는 것
이벤트는 하위 요소로 부터 상위 요소로 버블링되는 특성을 갖고 있지만, 이벤트 설정 시에 3번째 인수로 { capture : true }
를 작성한 이벤트가 있다면(=캡처를 걸어놓았다면) 하위 요소보다 해당 이벤트가 더 먼저 발생하고 그 다음 하위 요소부터 차례대로 이벤트가 발생한다.
event.stopPropagation()
이 있으면 이벤트가 정지된다.const parentEl = document.querySelector('.parent');
const handler = () => {
console.log('Parent!');
}
parnetEl = addEventListener('click', handler, { once : true });
핸들러의 실행 내용이 너무 많아서 기본 동작과 핸들러가 동시에 실행되면 과부하가 걸릴 것같을 때
const parentEl = document.querySelector('.parent');
parnetEl = addEventListener('wheel', () => {
for (i = 0; i < 10000; i++) {
console.log(i)
}
}, { passive : true });
html
<body>
<div class='parent'>
<div class='child'>1</div>
<div class='child'>2</div>
<div class='child'>3</div>
<div class='child'>4</div>
</div>
</body>
JS
const parentEl = document.querySelector('.parent');
const childEls = document.querySelectorAll('.child');
childEls.forEach(el => {
el.addEventListener('click', event => {
console.log(event.target.textContent);
})
})
JS
const parentEl = document.querySelector('.parent');
const childEls = document.querySelectorAll('.child');
parentEl.addEventListener('click', event => {
const childEl = event.target.closest('.child');
if (childEl) {
console.log(childEl.textContent);
}
});
const childEl = event.target.closest('.child');
event.target
: 클릭 이벤트가 발생한 요소 (= 즉, 클릭한 요소)event.target.closest('.child')
: 클릭한 요소 자기 자신을 포함해서 그 조상 요소들 중에 가장 가까운 child 라는 선택자를 가진 요소 (= 이 예제에서는 parentEl의 모든 자식 요소가 child 라는 클래스를 가지고 있기 때문에 그 어떤 요소를 선택하더라도 event.target
으로 자기 자신이 선택됨)두 방법의 코드 라인 수는 비슷하고 실행 결과는 동일하다.
하지만 실제로 메모리를 차지하는 방법에 있어서
모든 대상 요소에 이벤트 등록 방법은 총 4개의 이벤트 리스너가 생성되고
조상 요소에 이벤트 위임 방법은 1개의 이벤트 리스너만 생성된다.
지금은 이벤트 대상 요소가 4개뿐이라 메모리의 부담이 크지않을 수 있지만, 실제 프로젝트에선 다량의 이벤트를 제어하기 때문에 필요한 경우 이벤트 위임을 사용하는 것이 좋다.
이벤트 | 의미 |
---|---|
click | 클릭했을 때 |
dblclick | 더블클릭했을 때 |
mousedown | 버튼을 누를 때 |
mouseup | 버튼을 뗄 때 |
mouseenter | 포인터가 요소 위로 들어갈 때 |
mouseleave | 마우스를 요소 밖으로 나올 때 |
mousemove | 포인터가 움직일 때 |
contextmenu | 우클릭했을 때 |
wheel | 휠 버튼이 회전할 때 |
아래 JS 코드들을 붙여넣기 해보세요!
childEl.addEventListener('click', () => {
childEl.classList.toggle('active');
})
childEl.addEventListener('dblclick', () => {
childEl.classList.toggle('active');
})
childEl.addEventListener('mousedown', () => {
childEl.classList.add('active');
})
childEl.addEventListener('mouseup', () => {
childEl.classList.remove('active');
})
childEl.addEventListener('mouseenter', () => {
childEl.classList.add('active');
})
childEl.addEventListener('mouseleave', () => {
childEl.classList.remove('active');
})
childEl.addEventListener('mousemove', () => {
console.log(event.offsetX, event.offsetY)
})
childEl.addEventListener('contextmenu', () => {
event.preventDefault();
console.log(event);
})
parentEl.addEventListener('wheel', () => {
console.log(event);
})
이벤트 | 의미 |
---|---|
keydown | 키를 누를 때 |
keyup | 키를 뗄 때 |
const inputEl = document.querySelector('input');
inputEl.addEventListener('keydown', event => {
if (event.key === 'Enter') {
console.log(event.target.value);
}
})
영어를 입력할 때는 괜찮은데, CJK 문자(중국어, 일본어, 한국어)를 입력하고 엔터키를 누르면 콘솔에 같은 문자가 2번 출력됨
이걸 해결하기 위해 event.isComposing
속성을 사용
const inputEl = document.querySelector('input');
inputEl.addEventListener('keydown', event => {
if (event.key === 'Enter' && !event.isComposing) {
console.log(event.target.value);
}
})
이벤트 | 의미 |
---|---|
focus | 요소가 포커스를 얻었을 때 |
blur | 요소가 포커스를 잃었을 때 |
input | 값이 변경되었을 때 |
change | 상태가 변경되었을 때 (focus ↔️ blur) |
submit | 제출 버튼을 선택했을 때 |
reset | 리셋 버튼을 선택했을 때 |
아래 JS 코드들을 붙여넣기 해보세요!
const inputEls = document.querySelectorAll('input');
inputEls.forEach(el => {
el.addEventListener('focus', () => {
formEl.classList.add('active');
})
el.addEventListener('blur', () => {
formEl.classList.remove('active');
})
el.addEventListener('input', event => {
console.log(event.target.value);
})
el.addEventListener('change', event => {
console.log(event.target.value);
})
})
focus / blur
: inputEl가 포커스를 얻거나 잃으면 active 클래스가 추가되거나 제거된다 (➡️ formEl의 border 색이 바뀐다)input
: inputEl의 각 input에 변화가 생길 때마다 (새로운 값이 입력되거나...) 어떤 효과나 동작을 적용할 수 있음change
: inputEl의 각 input의 상태가 변할 때마다 (input창이 포커스를 얻거나 읽으면) 어떤 효과나 동작을 적용할 수 있음const formEl = document.querySelector('form');
formEl.addEventListener('submit', event => {
event.preventDefault();
const data = {
id : event.target[0].value,
pw : event.target[1].value
}
console.log(data);
})
formEl.addEventListener('reset', event => {
console.log('Reset!');
})
submit
: submit 이벤트가 발생하면 (= 이 예제에서는 제출 버튼을 누르면 submit 이벤트가 발생함) input값을 객체 형태로 저장하고 이것을 콘솔에 출력한다.preventDefault()
를 사용함)reset
: reset 이벤트가 발생하면 (= 이 예제에서는 리셋 버튼을 누르면 reset 이벤트가 발생함) 해당 페이지가 새로고침된다.이벤트를 강제로 발생시키는 것
child1.addEventListener('click', event => {
child2.dispatchEvent(new Event('click'));
child2.dispatchEvent(new Event('wheel'));
child2.dispatchEvent(new Event('keydown'));
})
const child1 = document.querySelector('.child:nth-child(1)');
const child2 = document.querySelector('.child:nth-child(2)');
child1.addEventListener('hello-world', event => {
console.log('커스텀 이벤트 발생!');
console.log(event.detail);
})
child2.addEventListener('click', () => {
child1.dispatchEvent(new Event('hello-world'));
})
hello-world
라는 이벤트는 원래 자바스크립트에는 존재하지 않는 이벤트. 즉, 사용자가 만들어낸 커스텀 이벤트.hello-world
이벤트를 발생시킬 수 있다.hello-world
이벤트는 커스텀 이벤트이므로 detail 속성에 내용이 없다. 생성자 함수를 통해 값을 넣어줄 수 있다.child2.addEventListener('click', () => {
child1.dispatchEvent(new CustomEvent('hello-world', {
detail : 123
}));
})
detail
이라는 추가 옵션을 넣어줄 수 있다.