[JavaScript] 브라우저 이벤트 종류 및 사용
1. 이벤트(event)란?
이벤트는 사용자가 웹 페이지에서 행한 일련의 동작을 의미합니다. 예를들어 사용자가 버튼을 클릭했을 때, 웹 페이지에서 그에 맞는 반응을 하여야 한다는 것입니다. 이를 위해 이벤트는 일반적으로 함수에 연결되며 그 함수는 이벤트가 발생하기 전에는 실행되지 않다가 이벤트가 발생하면 실행됩니다.
2. 이벤트 종류
1) UI 이벤트
Event | Description |
load | 웹페이지나 스크립트의 로드가 완료되었을 때 |
unload | 웹페이지가 언로드 될 때(주로 새로운 페이지를 요청한 경우) |
error | 브라우저가 자바스크립트 오류를 만났거나 요청한 자원이 존재하지 않을 때 |
resize | 브라우저 창의 크기를 조절했을 때 |
resize | 사용자가 페이지를 위 아래로 스크롤 할 때 |
select | 텍스트를 선택했을 때 |
2) 키보드(Keyboard) 이벤트
Event | Description |
keydown | 키를 누르고 있을 때 |
keyup | 누르고 있던 키를 뗄 때 |
keypress | 키를 누르고 뗏을 때 |
keyCode | 키 코드값 |
3) 마우스(Mouse) 이벤트
Event | Description |
click | 마우스 버튼을 클릭했을 때 |
dbclick | 마우스 버튼을 더블 클릭했을 때 |
mousedown | 마우스 버튼을 누르고 있을 때 |
mouseup | 누르고 있던 마우스 버튼을 뗄 때 |
mousemove | 마우스를 움직일 때 (터치스크린에서 동작하지 않는다) |
mouseover | 마우스를 요소 위로 움직였을 때 (터치스크린에서 동작하지 않는다) |
mouseout | 마우스를 요소 밖으로 움직였을 때 (터치스크린에서 동작하지 않는다) |
mouserenter | 해당 요소에 마우스 커서를 올려다놓았을때 |
mouseleave | 해당 요소에 마우스 커서를 빼낼때 |
4) 폼(Form) 이벤트
Event | Description |
input | input 또는 textarea 요소의 값이 변경되었을 때 contenteditable 어트리뷰트를 가진 요소의 값이 변경되었을 때 |
change | select box, checkbox, radio button의 상태가 변경되었을 때 |
submit | form을 submit할 때 (버튼 또는 키) |
reset | reset 버튼을 클릭할 때 (최근에는 사용 안함) |
5) 클립보드(Clipboard) 이벤트
Event | Description |
cut | 콘텐츠를 잘라내기할 때 |
copy | 콘텐츠를 복사할 때 |
paste | 콘텐츠를 붙여넣기할 때 |
6) 포커스(Focus) 이벤트
Event | Description |
focus/focusin | 요소가 포커스를 얻었을 때 |
blur/foucusout | 요소가 포커스를 잃었을 때 |
3. 이벤트 핸들러 등록 방법
1) 프로퍼티 방식
프로퍼티 리스너 방식은 이벤트 대상에 해당하는 객체의 프로퍼티로 이벤트를 등록하는 방식이다. 이벤트 핸들러 프로퍼티 방식은 이벤트에 오직 하나의 이벤트 핸들러만을 바인딩할 수 있다.
<!DOCTYPE html>
<html>
<body>
<button class="btn">Click me</button>
<script>
const btn = document.querySelector('.btn');
// 이벤트 핸들러 프로퍼티 방식은 이벤트에 하나의 이벤트 핸들러만을 바인딩할 수 있다
// 첫번째 바인딩된 이벤트 핸들러 => 실행되지 않는다.
btn.onclick = function () {
alert('① Button clicked 1');
};
// 두번째 바인딩된 이벤트 핸들러
btn.onclick = function () {
alert('① Button clicked 2');
};
// addEventListener 메소드 방식
// 첫번째 바인딩된 이벤트 핸들러
btn.addEventListener('click', function () {
alert('② Button clicked 1');
});
// 두번째 바인딩된 이벤트 핸들러
btn.addEventListener('click', function () {
alert('② Button clicked 2');
});
</script>
</body>
</html>
2) AddEventListener 메소드 방식
addEventListener 메소드를 이용하여 대상 DOM 요소에 이벤트를 바인딩하고 해당 이벤트가 발생했을 때 실행될 콜백 함수(이벤트 핸들러)를 지정한다. addEventListener 함수 방식은 이전 방식에 비해 아래와 같이 보다 나은 장점을 갖는다.
- 하나의 이벤트에 대해 하나 이상의 이벤트 핸들러를 추가할 수 있다.
- 캡처링과 버블링을 지원한다.
- HTML 요소뿐만아니라 모든 DOM 요소(HTML, XML, SVG)에 대해 동작한다. 브라우저는 웹 문서(HTML, XML, SVG)를 로드한 후, 파싱하여 DOM을 생성한다.
<input type="button" id="target1" value="button1" />
<input type="button" id="target2" value="button2" />
<script>
var t1 = document.getElementById('target1');
var t2 = document.getElementById('target2');
function btn_listener(event){
switch(event.target.id){
case 'target1':
alert(1);
break;
case 'target2':
alert(2);
break;
}
}
t1.addEventListener('click', btn_listener); // btn_listener()가 아니다.
t2.addEventListener('click', "btn_listener()"); // btn_listener()를 쓰려면 문자열로 감싼다.
</script>
4. 이벤트 핸들러 내부의 this 사용
1) 이벤트 핸들러 프로퍼티 방식
이벤트 핸들러 프로퍼티 방식에서 이벤트 핸들러는 메소드이므로 이벤트 핸들러 내부의 this는 이벤트에 바인딩된 요소를 가리킨다. 이것은 이벤트 객체의 currentTarget 프로퍼티와 같다.
<button class="btn">Button</button>
<script>
const btn = document.querySelector('.btn');
btn.onclick = function (e) {
console.log(this); // <button id="btn">Button</button>
console.log(e.currentTarget); // <button id="btn">Button</button>
console.log(this === e.currentTarget); // true
};
</script>
2) AddEventListener 방식
addEventListener 메소드에서 지정한 이벤트 핸들러는 콜백 함수이지만 이벤트 핸들러 내부의 this는 이벤트 리스너에 바인딩된 요소(currentTarget)를 가리킨다. 이것은 이벤트 객체의 currentTarget 프로퍼티와 같다.
<button class="btn">Button</button>
<script>
const btn = document.querySelector('.btn');
btn.addEventListener('click', function (e) {
console.log(this); // <button id="btn">Button</button>
console.log(e.currentTarget); // <button id="btn">Button</button>
console.log(this === e.currentTarget); // true
});
</script>
5. 이벤트 흐름
계층적 구조에 포함되어 있는 HTML 요소에 이벤트가 발생할 경우 연쇄적 반응이 일어난다. 즉, 이벤트가 전파(Event Propagation)되는데 전파 방향에 따라 버블링(Event Bubbling)과 캡처링(Event Capturing)으로 구분할 수 있다.
자식 요소에서 발생한 이벤트가 부모 요소로 전파되는 것을 버블링이라 하고, 자식 요소에서 발생한 이벤트가 부모 요소부터 시작하여 이벤트를 발생시킨 자식 요소까지 도달하는 것을 캡처링이라 한다. 주의할 것은 버블링과 캡처링은 둘 중에 하나만 발생하는 것이 아니라 캡처링부터 시작하여 버블링으로 종료한다는 것이다. 즉, 이벤트가 발생했을 때 캡처링과 버블링은 순차적으로 발생한다.
6. 이벤트 객체
event 객체는 이벤트를 발생시킨 요소와 발생한 이벤트에 대한 유용한 정보를 제공한다. 이벤트가 발생하면 event 객체는 동적으로 생성되며 이벤트를 처리할 수 있는 이벤트 핸들러에 인자로 전달된다.
<!DOCTYPE html>
<html>
<body>
<em class="message"></em>
<script>
function showCoords(e, msg) {
msg.innerHTML =
'clientX value: ' + e.clientX + '<br>' +
'clientY value: ' + e.clientY;
}
const msg = document.querySelector('.message');
addEventListener('click', function (e) {
showCoords(e, msg);
});
</script>
</body>
</html>
See the Pen Untitled by 김호링 (@caxepeyi-the-builder) on CodePen.
위와 같이 event 객체는 이벤트 핸들러에 암묵적으로 전달된다. 그러나 이벤트 핸들러를 선언할 때, event 객체를 전달받을 첫번째 매개변수를 명시적으로 선언하여야 한다. 예제에서 e라는 이름으로 매개변수를 지정하였으나 다른 매개변수 이름을 사용하여도 상관없다.
<!DOCTYPE html>
<html>
<body>
<em class="message"></em>
<script>
function showCoords(e, msg) {
msg.innerHTML =
'clientX value: ' + e.clientX + '<br>' +
'clientY value: ' + e.clientY;
}
const msg = document.querySelector('.message');
addEventListener('click', function (e) {
showCoords(e, msg);
});
</script>
</body>
</html>
7. Event 객체 속성 종류
1) Event.target
실제로 이벤트를 발생시킨 요소를 가리킨다.
<!DOCTYPE html>
<html>
<body>
<div class="container">
<button id="btn1">Hide me 1</button>
<button id="btn2">Hide me 2</button>
</div>
<script>
function hide(e) {
e.target.style.visibility = 'hidden';
// 동일하게 동작한다.
// this.style.visibility = 'hidden';
}
document.getElementById('btn1').addEventListener('click', hide);
document.getElementById('btn2').addEventListener('click', hide);
</script>
</body>
</html>
See the Pen Untitled by 김호링 (@caxepeyi-the-builder) on CodePen.
hide 함수를 특정 노드에 한정하여 사용하지 않고 범용적으로 사용하기 위해 event 객체의 target 프로퍼티를 사용하였다. 위 예제의 경우, hide 함수 내부의 e.target은 언제나 이벤트가 바인딩된 요소를 가리키는 this와 일치한다. 하지만 버튼별로 이벤트를 바인딩하고 있기 때문에 버튼이 많은 경우 위 방법은 바람직하지 않아 보인다.
<!DOCTYPE html>
<html>
<body>
<div class="container">
<button id="btn1">Hide me 1</button>
<button id="btn2">Hide me 2</button>
</div>
<script>
const container = document.querySelector('.container');
function hide(e) {
// e.target은 실제로 이벤트를 발생시킨 DOM 요소를 가리킨다.
e.target.style.visibility = 'hidden';
// this는 이벤트에 바인딩된 DOM 요소(.container)를 가리킨다. 따라서 .container 요소를 감춘다.
// this.style.visibility = 'hidden';
}
container.addEventListener('click', hide);
</script>
</body>
</html>
위 예제의 경우, this는 이벤트에 바인딩된 DOM 요소(.container)를 가리킨다. 따라서 container 요소를 감춘다. e.target은 실제로 이벤트를 발생시킨 DOM 요소(button 요소 또는 .container 요소)를 가리킨다. Event.target은 this와 반드시 일치하지는 않는다.
2) Event.currentTarget
이벤트에 바인딩된 DOM 요소를 가리킨다. 즉, addEventListener 앞에 기술된 객체를 가리킨다. addEventListener 메소드에서 지정한 이벤트 핸들러 내부의 this는 이벤트에 바인딩된 DOM 요소를 가리키며 이것은 이벤트 객체의 currentTarget 프로퍼티와 같다. 따라서 이벤트 핸들러 함수 내에서 currentTarget과 this는 언제나 일치한다.
<!DOCTYPE html>
<html>
<head>
<style>
html, body { height: 100%; }
div { height: 100%; }
</style>
</head>
<body>
<div>
<button>배경색 변경</button>
</div>
<script>
function bluify(e) {
// this: 이벤트에 바인딩된 DOM 요소(div 요소)
console.log('this: ', this);
// target: 실제로 이벤트를 발생시킨 요소(button 요소 또는 div 요소)
console.log('e.target:', e.target);
// currentTarget: 이벤트에 바인딩된 DOM 요소(div 요소)
console.log('e.currentTarget: ', e.currentTarget);
// 언제나 true
console.log(this === e.currentTarget);
// currentTarget과 target이 같은 객체일 때 true
console.log(this === e.target);
// click 이벤트가 발생하면 이벤트를 발생시킨 요소(target)과는 상관없이 this(이벤트에 바인딩된 div 요소)의 배경색이 변경된다.
this.style.backgroundColor = '#A5D9F3';
}
// div 요소에 이벤트 핸들러가 바인딩되어 있다.
// 자식 요소인 button이 발생시킨 이벤트가 버블링되어 div 요소에도 전파된다.
// 따라서 div 요소에 이벤트 핸들러가 바인딩되어 있으면 자식 요소인 button이 발생시킨 이벤트를 div 요소에서도 핸들링할 수 있다.
document.querySelector('div').addEventListener('click', bluify);
</script>
</body>
</html>
See the Pen Untitled by 김호링 (@caxepeyi-the-builder) on CodePen.
3) Event.type
발생한 이벤트의 종류를 나타내는 문자열을 반환한다.
<!DOCTYPE html>
<html>
<body>
<p>키를 입력하세요</p>
<em class="message"></em>
<script>
const body = document.querySelector('body');
function getEventType(e) {
console.log(e);
document.querySelector('.message').innerHTML = `${e.type} : ${e.keyCode}`;
}
body.addEventListener('keydown', getEventType);
body.addEventListener('keyup', getEventType);
</script>
</body>
</html>
See the Pen Untitled by 김호링 (@caxepeyi-the-builder) on CodePen.
4) Event.cancelable
요소의 기본 동작을 취소시킬 수 있는지 여부(true/false)를 나타낸다.
<!DOCTYPE html>
<html>
<body>
<a href="poiemaweb.com">Go to poiemaweb.com</a>
<script>
const elem = document.querySelector('a');
elem.addEventListener('click', function (e) {
console.log(e.cancelable);
// 기본 동작을 중단시킨다.
e.preventDefault();
});
</script>
</body>
</html>
See the Pen Untitled by 김호링 (@caxepeyi-the-builder) on CodePen.
5) Event.eventPhase
이벤트 흐름 상에서 어느 단계에 있는지를 반환한다.
반환값 | 의미 |
0 | 이벤트 없음 |
1 | 캡쳐링 단계 |
2 | 타깃 |
3 | 버블링 단계 |
8. 이벤트 위임(Event Delegation)
만일 클릭 이벤트에 반응하는 처리를 100개 구현 해야하는 경우 100개의 이벤트 핸들러를 바인딩해야 할까? 만약 이렇게 한다면 실행 속도 저하의 원인이 될 뿐 아니라 코드 또한 매우 길어지며 작성 또한 불편하다. 그리고 동적으로 요소가 추가되는 경우, 아직 추가되지 않은 요소는 DOM에 존재하지 않으므로 이벤트 핸들러를 바인딩할 수 없다. 이러한 경우 이벤트 위임을 사용한다.
이벤트 위임(Event Delegation)은 다수의 자식 요소에 각각 이벤트 핸들러를 바인딩하는 대신 하나의 부모 요소에 이벤트 핸들러를 바인딩하는 방법이다. 자식 요소에 각각 이벤트 핸들러를 바인딩하는 것 대신 부모 요소에 이벤트 핸들러를 바인딩하는 것이다. 또한 DOM 트리에 새로운 요소를 추가하더라도 이벤트 처리는 부모 요소에 위임되었기 때문에 새로운 요소에 이벤트를 핸들러를 다시 바인딩할 필요가 없다. 이는 이벤트가 이벤트 흐름에 의해 이벤트를 발생시킨 요소의 부모 요소에도 영향(버블링)을 미치기 때문에 가능한 것이다.
<!DOCTYPE html>
<html>
<body>
<ul class="post-list">
<li id="post-1">Item 1</li>
<li id="post-2">Item 2</li>
<li id="post-3">Item 3</li>
<li id="post-4">Item 4</li>
<li id="post-5">Item 5</li>
<li id="post-6">Item 6</li>
</ul>
<div class="msg">
<script>
const msg = document.querySelector('.msg');
const list = document.querySelector('.post-list')
list.addEventListener('click', function (e) {
// 이벤트를 발생시킨 요소
console.log('[target]: ' + e.target);
// 이벤트를 발생시킨 요소의 nodeName
console.log('[target.nodeName]: ' + e.target.nodeName);
// li 요소 이외의 요소에서 발생한 이벤트는 대응하지 않는다.
if (e.target && e.target.nodeName === 'LI') {
msg.innerHTML = 'li#' + e.target.id + ' was clicked!';
}
});
</script>
</body>
</html>
See the Pen Untitled by 김호링 (@caxepeyi-the-builder) on CodePen.
9. 기본 동작의 변경
이벤트 객체는 요소의 기본 동작과 요소의 부모 요소들이 이벤트에 대응하는 방법을 변경하기 위한 메소드는 가지고 있다.
1) Event.preventDefault()
폼을 submit하거나 링크를 클릭하면 다른 페이지로 이동하게 된다. 이와 같이 요소가 가지고 있는 기본 동작을 중단시키기 위한 메소드가 preventDefault()이다.
<!DOCTYPE html>
<html>
<body>
<a href="http://www.google.com">go</a>
<script>
document.querySelector('a').addEventListener('click', function (e) {
console.log(e.target, e.target.nodeName);
// a 요소의 기본 동작을 중단한다.
e.preventDefault();
});
</script>
</body>
</html>
2) Event.stopPropagation()
어느 한 요소를 이용하여 이벤트를 처리한 후 이벤트가 부모 요소로 이벤트가 전파되는 것을 중단시키기 위한 메소드이다. 부모 요소에 동일한 이벤트에 대한 다른 핸들러가 지정되어 있을 경우 사용된다. 아래 코드를 보면, 부모 요소와 자식 요소에 모두 mousedown 이벤트에 대한 핸들러가 지정되어 있다. 하지만 부모 요소와 자식 요소의 이벤트를 각각 별도로 처리하기 위해 button 요소의 이벤트의 전파(버블링)를 중단시키기 위해서는 stopPropagation 메소드를 사용하여 이벤트 전파를 중단할 필요가 있다.
<!DOCTYPE html>
<html>
<head>
<style>
html, body { height: 100%;}
</style>
</head>
<body>
<p>버튼을 클릭하면 이벤트 전파를 중단한다. <button>버튼</button></p>
<script>
const body = document.querySelector('body');
const para = document.querySelector('p');
const button = document.querySelector('button');
// 버블링
body.addEventListener('click', function () {
console.log('Handler for body.');
});
// 버블링
para.addEventListener('click', function () {
console.log('Handler for paragraph.');
});
// 버블링
button.addEventListener('click', function (event) {
console.log('Handler for button.');
// 이벤트 전파를 중단한다.
event.stopPropagation();
});
</script>
</body>
</html>
- 참고문헌