브라우저에서 이벤트
란 웹 브라우저가 알려주는 HTML 요소에 대한 사건의 발생을 의미한다. 사용자(혹은 브라우저)가 버튼을 클릭하거나 input에 값을 입력하는 등의 HTML 페이지를 조작할 때 발생하는 것을 이벤트라고 한다.
웹 페이지에 사용된 자바스크립트는 이렇게 발생한 이벤트에 반응하여 특정 동작을 수행할 수 있으며 이를 통해 사용자와 웹페이지 간 인터랙션이 가능하게 된다.
브라우저 이벤트 종류 중 사용 빈도가 높은 이벤트 타입
이벤트 종류 | 이벤트 타입 | 이벤트 발생 시점 |
---|---|---|
마우스 이벤트 | click | 마우스 클릭했을 때 |
dbclick | 마우스 더블 클릭했을 때 | |
mousedown | 마우스 버튼 눌렀을 때 | |
mouseup | 누르고 있던 마우스 버튼 놓았을때 | |
mousemove | 마우스 커서 움직였을때 | |
mouseenter | 마우스 커서를 HTML 요소 안으로 이동했을때(버블링 x) | |
mouseover | 마우스 커서를 HTML 요소 안으로 이동했을때(버블링 o) | |
mouseleave | 마우스 커서를 HTML 요소 밖으로 이동했을때 (버블링 x) | |
mouseout | 마우스 커서를 HTML 요소 밖으로 이동했을때 (버블링 o) | |
키보드 이벤트 | keydown | 키보드 키 눌렀을때 발생. 단, 문자, 숫자, 특수 문자, enter 키 눌렀을때는 연속적으로 발생하고 그 외 키는 한번만 발생 |
keyup | 누르고 있던 키 놓았을때 한 번만 발생 | |
포커스 이벤트 | focus | HTML 요소가 포커스 받았을때(버블링 x) |
blur | HTML 요소가 포커스를 잃었을때 (버블링 x) | |
focusin | HTML 요소가 포커스 받았을때(버블링 o) | |
focusout | HTML 요소가 포커스를 잃었을때 (버블링 o) | |
폼 이벤트 | submit | form 요소 내 submit 버튼 클릭 했을때 |
reset | form 요소 내 reset 버튼 클릭 했을때 | |
값 변경 이벤트 | input | input(text, checkbox, radio), select, textarea 요소의 값이 입력되었을때 |
change | input(text, checkbox, radio), select, textarea 요소의 값이 변경되었을때(사용자가 입력을 종료하고 요소가 포커스를 잃었을때) | |
readystatechange | HTML 문서의 로드와 파싱 상태를 나타내는 document.readyState 프로퍼티 값(’loading’, ‘interactive’, ‘complete’)이 변경될 때 | |
DOM 뮤테이션 이벤트 | DOMContentLoaded | HTML 문서의 로드와 파싱이 완료되어 DOM 생성이 완료되었을때 |
뷰 이벤트 | resize | 브라우저 윈도우의 크기를 리사이즈할때 연속적으로 발생(오직 window 객체에서만 발생) |
scroll | 웹페이지(document) 또는 HTML 요소를 스크롤할때 연속적으로 발생 | |
리소스 이벤트 | load | DOMContentLoaded 이벤트가 발생한 이후, 모든 리소스(이미지, 폰트 등)의 로딩이 완료되었을때(주로 window 객체에서 발생) |
unload | 리소스가 언로드될때(주로 새로운 웹페이지 요청한 경우) | |
abort | 리소스 로딩이 중단되었을때 | |
error | 리소스 로딩이 실패했을때 |
이벤트 핸들러란?
이벤트 발생시 호출되는 함수
이벤트 핸들러 등록이란?
이벤트 발생 시 브라우저에게 이벤트 핸들러 호출을 위임하는 것
이벤트가 언제 발생할 지 모르기 때문에 프로그래머가 함수를 호출하는 것이 아니고 브라우저가 이벤트를 감지해서 이벤트 핸들러를 호출할 수 있도록 프로그래머가 이벤트 핸들링을 브라우저에게 위임하는 것을 의미
on
+ 이벤트 타입 (ex. onclick
, onchange
)<body>
<button onclick="greeting()">Click</button>
<script>
function greeting(){
alert('Hello World')
}
</script>
</body>
어트리뷰트 onclick="greeting()"
를 파싱하면 자바스크립트 엔진이 아래와 같은 함수를 암묵적으로 생성한다.
function onclick(event){
greeting()
}
window 객체, Document, HTMLElement 타입의 DOM 노드 객체는 각각 이벤트에 대응하는 이벤트 핸들러 프로퍼티(ex. window.onload
, document.onreadystatechange
, input.onchange
등)를 갖고 있다.
// 이벤트 타겟
const $button = document.querySelector('button');
function greeting() {
alert('Hello World')
}
// 이벤트 핸들러 프로퍼티에 이밴트 핸들러 바인딩
$button.onclick = function () {
greeting()
}
메서드의 세번째 인자로 capture 사용 여부를 boolean 값으로 줄 수 있다. true를 줄 경우 후술할 캡쳐링 단계의 이벤트를 캐치한다.
$button.addEventListener('click', function(){
greeting()
})
<body>
<button>Click</button>
<p class="first"></p>
<p class="second"></p>
</body
const $button = document.querySelector('button');
const $text1 = document.querySelector(".first");
const $text2 = document.querySelector(".second");
// $button 요소에 첫번째 이벤트 핸들러 등록
$button.addEventListener("click", function () {
$text1.innerHTML = "첫번째 이벤트 핸들러 호출";
});
// $button 요소에 두번째 이벤트 핸들러 등록
$button.addEventListener("click", function () {
$text2.innerHTML = "두번째 이벤트 핸들러 호출";
});
//버튼을 클릭하면 두 이벤트 핸들러가 순차적으로 실행된다.
const $button = document.querySelector('button');
// $button 요소의 click 이벤트에 이벤트 핸들러 바인딩
// 이벤트 프로퍼티 방식
$button.onclick = function () {
console.log('onclick 프로퍼티 button click')
}
// addEventListener 방식
$button.addEventListener('click', function(){
console.log('addEventListener 메서드 button click')
})
// 버튼을 클릭하면 두 개의 이벤트 핸들러가 차례로 호출된다.
removeEventListener
메서드function sayHi(){
console.log('hi')
}
function handleClick(){
console.log('click')
}
$button.addEventListener('click', sayHi)
$button.addEventListener('click', handleClick)
$button.removeEventListener('click', handleClick) // 이벤트 핸들러 제거됨
위 코드를 실행하여 버튼을 클릭하면 첫번째 이벤트 핸들러인 sayHi 함수만 호출되어 "hi"만 콘솔에 출력된다. 두번째 이벤트 핸들러인 handleClick 함수는 removeEventListener
메서드로 제거되었으므로 클릭 이벤트가 발생해도 호출되지 않는다.
// 무명 함수를 이벤트 핸들러로 등록한 경우
$button.addEventListener('click', () => console.log('hi'))
//removeEventListener가 해당 무명함수를 참조할 수 없으므로 등록된 이벤트 핸들러를 제거할 수 없다.
// $button.removeEventListener('click', () => console.log('hi'))
제거하지 못하는 이유
'hi'를 콘솔에 출력한다는 함수의 기능은 동일하지만 addEventListener
메소드로 등록한 이벤트 핸들러 () => console.log('hi')
와 removeEventListner
메소드로 제거하려는 이벤트 핸들러 () => console.log('hi')
는 다른 함수이기 때문이다.
프로퍼티에 null
을 할당한다.
function handleClick(){
console.log('click')
}
$button.onclick = handleClick;
$button.removeEventListener('click', handleClick) // 제거되지 않는다.
$button.onclick = null; // 이벤트 핸들러 제거됨
event
를 사용해야한다.<!-- event가 아닌 다른 매개변수명을 사용하면 에러 발생 -->
<button onclick="console.log(v)">매개변수 v</button>
<button onclick="console.log(event)">매개변수 event</button>
공통 프로퍼티 | 설명 | 타입 | 추가 설명 |
---|---|---|---|
type | 이벤트 타입 | string | |
target | 이벤트를 발생시킨 DOM 요소 | DOM 요소 노드 | |
currentTarget | 이벤트 핸들러가 바인딩된 DOM 요소 | DOM 요소 노드 | |
eventPhase | 이벤트 전파 단계 | number | 0 : 이벤트 없음 1 : 캡처링 단계 2 : 타깃 단계 3 : 버블링 단계 |
bubbles | 이벤트를 버블링으로 전파하는지 여부 | boolean | bubbles: false(버블링 안함)인 이벤트들 - 포커스 이벤트(focus/blur) - 마우스 이벤트(mouseenter/mouseleave) - 리소스 이벤트(load/unload/abort/error) |
cancelable | preventDefault 메서드로 이벤트 기본 동작 취소가능한지 여부 | boolean | cancelable: false로 취소할 수 없는 이벤트들 - 포커스 이벤트(focus/blur) - 마우스 이벤트(mouseenter/mouseleave) - 리소스 이벤트(load/unload/abort/error) |
defaultPrevented | preventDefault 메서드로 이벤트 취소했는지 여부 | boolean | |
isTrusted | 사용자 행위에 의해 발생한 이벤트인지 여부 | boolean | |
timeStamp | 이벤트가 발생한 시각 | number |
👉 DOM 트리 상에 존재하는 DOM 요소 노드에서 발생한 이벤트가 DOM 트리를 통해 전파되는 것
DOM의 최상단에서부터 이벤트 타겟 노드로, DOM 트리의 위에서 아래로 전파
<body>
<div id="container">
<p>
<button>Click</button>
</p>
</div>
</body>
const $div = document.getElementById("container");
const $para = document.querySelector("p");
const $button = document.querySelector("button");
const handleClick = (e) => {
console.log(
`이벤트 타겟: ${e.target.nodeName} / currentTarget: ${e.currentTarget.nodeName} / 이벤트 전파 단계: ${e.eventPhase}`
);
};
// addEventListener 메서드의 세번째 인자에 true를 넘겨주면 캡쳐링을 감지한다.
$div.addEventListener("click", handleClick, true);
$para.addEventListener("click", handleClick, true);
$button.addEventListener("click", handleClick, true);
이벤트 타겟 노드에서 상위 요소로, DOM 트리의 하위에서 상위로 전파
// caturing: true값을 false로 넣어주거나 생략하면 버블링을 감지한다.
$div.addEventListener("click", handleClick);
$para.addEventListener("click", handleClick);
$button.addEventListener("click", handleClick);
stopPropagation
메서드를 사용해서 이벤트 전파를 중단시킬 수 있다.
// 이벤트 전파 방지하는 이벤트 핸들러 생성
const stopHandler = (e) => {
console.log(e.target.nodeName);
e.stopPropagation();
}
$div.addEventListener('click', handleClick);
$para.addEventListener('click', stopHandler); // $para 요소의 상위 요소인 $div 요소로의 이벤트 전파를 중단한다.
$button.addEventListener('click', handleClick);
이벤트 타겟인 $button부터 이벤트 버블링이 시작되어 다음 상위 요소인 $para 요소에 click 이벤트가 전파 되었을때 stopPropagation
메서드가 실행되어 그 다음 상위 요소로의 이벤트 전파를 중단한다. 따라서 click 이벤트가 상위 요소인 $div 요소에 전달되지 않기 때문에 해당 요소의 이벤트 핸들러인 handleClick은 호출되지 않는다.
이벤트 전파의 특성을 활용하여, 여러 개의 하위 DOM 요소에 일일이 이벤트 핸들러를 등록하는 것이 아닌 상위 요소에 이벤트 핸들러를 등록, 즉 상위 요소에 이벤트를 위임하여 하위 요소들의 이벤트를 처리할 수 있다.
li 요소를 클릭하면 해당 요소에 class를 추가하여 font-weight를 bold로 변경해주는 예시를 들어보자.
<head>
<style>
.active {
font-weight: bold;
}
</style>
</head>
<body>
<ul id="colors">
<li class="red">Red</li>
<li class="yellow">Yellow</li>
</ul>
</body>
이벤트 위임 안한 경우
const $colors = document.getElementById('colors')
// $colors 요소의 자식 요소들인 li 요소들을 클릭했을 때, active라는 class를 추가해주는 함수
function activate({ target }){
[...$colors.children].forEach($color => {
$color.classList.toggle('active', $color === target )
}
// $colors 요소의 자식 요소에 각각 이벤트 핸들러 등록
document.querySelector('.red').addEventListener('click', activate)
document.querySelector('.yellow').addEventListener('click', activate)
// li 요소 추가
const li = document.createElement("li");
const text = document.createTextNode("Green");
li.appendChild(text);
li.setAttribute("class", "green");
$colors.appendChild(li);
마지막에 동적으로 li 요소(Green)를 추가했는데 이 요소를 클릭해도 아무 변화가 없다. 이벤트 리스너를 달아주지 않았기 때문이다.
이벤트 위임한 경우
const $colors = document.getElementById('colors')
function activate({ target }){
[...$colors.children].forEach($color => {
$color.classList.toggle('active', $color === target)
}
// li 요소 추가
const li = document.createElement("li");
const text = document.createTextNode("Green");
li.appendChild(text);
li.setAttribute("class", "green");
$colors.appendChild(li);
// 자식 요소가 아닌 상위 요소 $colors에 이벤트 핸들러 등록
$colors.addEventListener("click", activate);
$colors 요소의 자식 요소인 li 요소가 아닌, $colors 요소에 이벤트 핸들러를 등록했다. 이러한 이벤트 위임에 따라 동적으로 추가한 li요소(Green)를 클릭하면 이벤트 핸들러인 activate 함수가 호출되어 font-weight가 bold로 변한다.
이처럼 이벤트 위임을 활용하면 동적으로 추가한 자식 요소에 이벤트 핸들러를 등록하지 않아도 상위 요소가 자식 요소의 이벤트를 감지해서 이벤트 핸들러가 호출된다.
참고자료
https://developer.mozilla.org/en-US/docs/Web/Events
https://poiemaweb.com/js-event
https://joshua1988.github.io/web-development/javascript/event-propagation-delegation/
https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/toggle#setting_the_force_parameter
http://www.tcpschool.com/javascript/js_event_concept