브라우저 이벤트
이벤트 기초
Event는 무언가 일어났다는 신호입니다.
이벤트의 종류
마우스 이벤트
click - 요소 위에서 마우스 왼쪽 버튼을 눌렀을 때 발생
contextmenu - 요소 위에서 마우스 오른쪽 버튼을 눌렀을 때 발생
mouseover - 마우스 커서를 요소 위로 움직였을 때 발생
mouseout - 마우스 커서가 요소 밖으로 움직였을 때 발생
mousedown - 요소 위에서 마우스 왼쪽 버튼을 누르고 있을 때 발생
mouseup - 요소 위에서 마우스 왼쪽 버튼을 뗄때 발생
폼 요소 이벤트
submit - 사용자가 form을 제출할 때 발생
focus - 사용자가 input과 같은 요소에 포커스할 때 발생
키보드 이벤트
keydown - 사용자가 키보드 버튼을 누를 때 발생
keyup - 사용자가 키보드 버튼을 뗄 때 발생
문서 이벤트
DomContentLoaded - HTML이 전부 로드 및 처리되어 Dom 생성이 완료될 때 발생
CSS 이벤트
transitionend - CSS 애니메이션이 종료 될 때 발생
이벤트 핸들러
핸들러는 사용자의 행동에 어떻게 반응할지를 자바스크립트 코드로 표현한 것입니다.
예제) HTML 속성과 DOM 프로퍼티 적용 예시
// HTML 속성
<input value="클릭해 주세요." onclick="alert('클릭!')" type="button">
<script>
function countRabbits() {
for(let i=1; i<=3; i++) {
alert(`토끼 ${i}마리`);
}
}
</script>
<input type="button" onclick="countRabbits()" value="토끼를 세봅시다!">
// DOM 프로퍼티
<input id="elem" type="button" value="클릭해 주세요.">
<script>
elem.onclick = function() {
alert('감사합니다.');
};
</script>
addEventListener
element.addEventListener(event, handler, [options]);
event: 이벤트 이름(ex) click)
handler: 핸들러 함수
options:
- once: true이면 이벤트가 트리거 될 때 리스너가 자동으로 삭제됩니다.
- capture: 어느 단계에서 이벤트를 다뤄야하는지 알려줍니다.
- passive: true 이면 리스너에서 지정한 함수가 preventDefault()를 호출하지 않습니다.
버블링과 캡처링
버블링
한 요소에 이벤트가 발생하면, 이 요소에 할당된 핸들러가 동작하고, 이어서 부모 요소의 핸들러가 동작합니다. 가장 최상단의 조상 요소를 만날 때까지 이 과정이 반복되면서 요소 각각에 할당된 핸들러가 동작합니다.
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
가장 안쪽의 <p>를 클릭하면 순서대로 다음과 같은 일이 벌어집니다.
1. <p>에 할당된 onclick 핸들러가 동작합니다.
2. 바깥의 <div>에 할당된 핸들러가 동작합니다.
3. 그 바깥의 <form>에 할당된 핸들러가 동작합니다.
4. document 객체를 만날 때까지, 각 요소에 할당된 onclick 핸들러가 동작합니다.
이런 동작 방식 때문에 <p> 요소를 클릭하면 p → div → form 순서로 3개의 얼럿 창이 뜨는것이죠.
이런 흐름을 '이벤트 버블링’이라고 부릅니다. 이벤트가 제일 깊은 곳에 있는 요소에서 시작해 부모 요소를 거슬러 올라가며 발생하는 모양이 마치 물속 거품(bubble)과 닮았기 때문입니다.
주의) 거의 모든 이벤트는 버블링 되고 focus이벤트와 같이 버블링 되지 않는 이벤트도 있습니다.
event.target
이벤트가 발생한 가장 안쪽의 요소는 타깃(target) 요소라고 불리고, event.target을 사용해 접근할 수 있습니다.
버블링 중단하기
이벤트 객체의 메서드인 event.stopPropagation()를 사용하면 됩니다.
아래 예시에서 <button>을 클릭해도 body.onclick은 동작하지 않습니다.
<body onclick="alert(`버블링은 여기까지 도달하지 못합니다.`)">
<button onclick="event.stopPropagation()">클릭해 주세요.</button>
</body>
캡처링
실제 코드에서 자주 쓰이지 않으므로 참조만 하시면 됩니다.
1. 캡처링 단계 – 이벤트가 하위 요소로 전파되는 단계
2. 타깃 단계 – 이벤트가 실제 타깃 요소에 전달되는 단계
3. 버블링 단계 – 이벤트가 상위 요소로 전파되는 단계
이벤트 위임
이벤트 위임을 사용하면 요소마다 핸들러를 할당하지 않고, 요소의 공통 조상에 이벤트 핸들러를 단 하나만 할당해도 여러 요소를 한꺼번에 다룰 수 있습니다.
활용 예제) 메뉴 전체에 핸들러를 하나 추가해주고 data-action 속성에 호출할 메서드를 할당해 한번에 처리
<div id="menu">
<button data-action="save">저장하기</button>
<button data-action="load">불러오기</button>
<button data-action="search">검색하기</button>
</div>
<script>
class Menu {
constructor(elem) {
this._elem = elem;
elem.onclick = this.onClick.bind(this); // (*)
}
save() {
alert('저장하기');
}
load() {
alert('불러오기');
}
search() {
alert('검색하기');
}
onClick(event) {
let action = event.target.dataset.action;
if (action) {
this[action]();
}
};
}
new Menu(menu);
</script>
브라우저 기본 동작 막기
event.preventDefault() 메서드를 사용합니다. 핸들러가 addEventListener가 아닌 on<event>를 사용해 할당되었다면 false를 반환하게 해 기본 동작을 막을 수도 있습니다.
addEventListener의 passive: true 옵션은 브라우저에게 preventDefault()를 호출하지 않겠다고 알리는 역할을 합니다.
브라우저는 스크롤링을 발생시키는 이벤트를 감지했을 때 먼저 모든 핸들러를 처리하는데, 이때 preventDefault가 어디에서도 호출되지 않았다고 판단되면, 그제야 스크롤링을 진행합니다. 이 과정에서 불필요한 지연이 생기고, 화면이 ‘덜덜 떨리는’ 현상이 발생합니다.
passive: true 옵션은 핸들러가 스크롤링을 취소하지 않을 것이라는 정보를 브라우저에게 알려주는 역할을 합니다.
커스텀 이벤트 디스패치
Event의 생성자
let event = new Event(type[, options]);
type
이벤트 타입을 나타내는 문자열로 "click"과 같은 내장 이벤트
options
bubbles: true/false – true인 경우 이벤트가 버블링 됩니다.
cancelable: true/false – true인 경우 브라우저 '기본 동작’이 실행되지 않습니다.
dispatchEvent
이벤트 객체를 생성한 다음엔 elem.dispatchEvent(event)를 호출해 요소에 있는 이벤트를 반드시 '실행’시켜줘야 합니다
이벤트 안 이벤트
이벤트는 대게 큐에서 처리됩니다. 따라서 브라우저가 onclick 이벤트를 처리하고 있는데 마우스를 움직여서 새로운 이벤트를 발생시키면 이 이벤트에 상응하는 mousemove 핸들러는 onclick 이벤트 처리가 끝난 후에 호출됩니다.