...
브라우저 이벤트 란?
이벤트(event)는 어떤 사건을 의미한다.
브라우저에서의 이벤트란 예를 들어 사용자가 버튼을 클릭했을 때, 웹페이지가 로드되었을 때와 같은 것인데 이것은 DOM 요소와 관련이 있다. 이벤트가 발생하면 그에 맞는 반응을 하여야 한다. 이를 위해 이벤트는 일반적으로 함수에 연결되며 그 함수는 이벤트가 발생하기 전에는 실행되지 않다가 이벤트가 발생되면 실행된다. 이 함수를 이벤트 핸들러라 하며 이벤트에 대응하는 처리를 기술한다.
자바스크립트 이벤트의 종류
실무에 자주 쓰이는 대표적인 자바스크립트 이벤트를 소개한다.
UI Event
Event | Description |
load | 웹페이지나 스크립트의 로드가 완료되었을 때 |
unload | 웹페이지가 언로드될 때(주로 새로운 페이지를 요청한 경우) |
error | 브라우저가 자바스크립트 오류를 만났거나 요청한 자원이 존재하지 않는 경우 |
resize | 브라우저 창의 크기를 조절했을 때 |
scroll | 사용자가 페이지를 위아래로 스크롤할 때 |
select | 텍스트를 선택했을 때 |
Keyboard Event
Event | Description |
keydown | 키를 누르고 있을 때 |
keyup | 누르고 있던 키를 뗄 때 |
keypress | 키를 누르고 뗏을 때 |
keyCode | 키 코드값. https://blog.lael.be/post/75 |
Mouse Event
Event | Description |
click | 마우스 버튼을 클릭했을 때 |
dbclick | 마우스 버튼을 더블 클릭했을 때 |
mousedown | 마우스 버튼을 누르고 있을 때 |
mouseup | 누르고 있던 마우스 버튼을 뗄 때 |
mousemove | 마우스를 움직일 때 (터치스크린에서 동작하지 않는다) |
mouseover | 마우스를 요소 위로 움직였을 때 (터치스크린에서 동작하지 않는다) |
mouseout | 마우스를 요소 밖으로 움직였을 때 (터치스크린에서 동작하지 않는다) |
mouserenter | 해당 요소에 마우스 커서를 올려다놓았을때 |
mouseleave | 해당 요소에 마우스 커서를 빼낼때 |
Focus Event
Event | Description |
focus/focusin | 요소가 포커스를 얻었을 때 |
blur/foucusout | 요소가 포커스를 잃었을 때 |
Form Event
Event | Description |
input | input 또는 textarea 요소의 값이 변경되었을 때 contenteditable 어트리뷰트를 가진 요소의 값이 변경되었을 때 |
change | select box, checkbox, radio button의 상태가 변경되었을 때 |
submit | form을 submit할 때 (버튼 또는 키) |
reset | reset 버튼을 클릭할 때 (최근에는 사용 안함) |
Clipboard Event
Event | Description |
cut | 콘텐츠를 잘라내기할 때 |
copy | 콘텐츠를 복사할 때 |
paste | 콘텐츠를 붙여넣기할 때 |
이벤트 핸들러 등록 방법
inline 방식
인라인 방식은 이벤트를 이벤트 대상의 태그 속성으로 지정하는 것이다.
다음은 버튼을 클릭했을 때 Hello world를 경고창으로 출력한다.
<input type="button" onclick="alert('Hello world');" value="button" />
이벤트가 발생한 대상을 필요로하는 경우 this를 통해서 참조할 수 있다.
<input type="button" onclick="alert('Hello world, ' + this.value);" value="button" />
// >> Hello world button
script탭에서 함수를 만들고, 태그에 지정하는 방식도 있다.
<button onclick="myHandler1(); myHandler2();">Click me</button>
<script>
function myHandler1() {
alert('myHandler1');
}
function myHandler2() {
alert('myHandler2');
}
</script>
프로퍼티 방식
프로퍼티 리스너 방식은 이벤트 대상에 해당하는 객체의 프로퍼티로 이벤트를 등록하는 방식이다.
인라인 방식에 비해서 HTML과 JavaScript를 분리할 수 있다는 점에서 선호되는 방식이지만 뒤에서 배울 addEventListener 방식을 추천한다.
이벤트 핸들러 프로퍼티 방식은 이벤트에 오직 하나의 이벤트 핸들러만을 바인딩할 수 있다
<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'); // << 실행결과
};
</script>
addEventListener 방식
addEventListener 메소드를 이용하여 대상 DOM 요소에 이벤트를 바인딩하고 해당 이벤트가 발생했을 때 실행될 콜백 함수(이벤트 핸들러)를 지정한다.
addEventListener 함수 방식은 이전 방식에 비해 아래와 같이 보다 나은 장점을 갖는다.
- 하나의 이벤트에 대해 하나 이상의 이벤트 핸들러를 추가할 수 있다.
- 캡처링과 버블링을 지원한다.
- HTML 요소뿐만아니라 모든 DOM 요소(HTML, XML, SVG)에 대해 동작한다. 브라우저는 웹 문서(HTML, XML, SVG)를 로드한 후, 파싱하여 DOM을 생성한다.
<input type="button" id="target" value="button" />
<script>
var t = document.getElementById('target');
t.addEventListener('click', function(event){
alert('Hello world, '+event.target.value);
});
</script>
<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>
위 코드에서 이벤트리스너 두번째 인수로 함수를 넣어주는데,addEventListener('click', btn_listener)보다시피btn_listener()가 아니라 그냥btn_listener다.
함수인데 ()를 안붙여주면 쓰나 싶은데,btn_listener()로 쓰면 함수자체를 쓰는게 아니라 리턴값을 받게된다.
만일btn_listener()라고 쓰고 싶다면 "btn_listener()" 문자열로 감싸면 된다. 문자열로 감싸면 자바스크립트 코드 자체로 인식하기 때문이다. 마치 eval()함수 처럼 말이다.
이벤트 핸들러 내부의 this 이용
인라인 이벤트 핸들러 방식
인라인 이벤트 핸들러 방식의 경우, 이벤트 핸들러는 일반 함수로서 호출되므로 이벤트 핸들러 내부의 this는 전역 객체 window를 가리킨다.
<button onclick="foo()">Button</button>
<script>
function foo () {
console.log(this); // window
}
</script>
이벤트 핸들러 프로퍼티 방식
이벤트 핸들러 프로퍼티 방식에서 이벤트 핸들러는 메소드이므로 이벤트 핸들러 내부의 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>
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>
Event 객체
event 객체는 이벤트를 발생시킨 요소와 발생한 이벤트에 대한 유용한 정보를 제공한다.
이벤트가 발생하면 event 객체는 동적으로 생성되며 이벤트를 처리할 수 있는 이벤트 핸들러에 인자로 전달된다.
<!DOCTYPE html>
<html>
<body>
<p>클릭하세요. 클릭한 곳의 좌표가 표시됩니다.</p>
<em class="message"></em>
<script>
function showCoords(e) { // e: event object
const msg = document.querySelector('.message');
msg.innerHTML =
'clientX value: ' + e.clientX + '<br>' +
'clientY value: ' + e.clientY;
}
addEventListener('click', showCoords); // 참조하는게 없으면 기본 window 전역 객체
</script>
</body>
</html>
위와 같이 event 객체는 이벤트 핸들러에 암묵적으로 전달된다. 그러나 이벤트 핸들러를 선언할 때, event 객체를 전달받을 첫번째 매개변수를 명시적으로 선언하여야 한다.
예제에서 e라는 이름으로 매개변수를 지정하였으나 다른 매개변수 이름을 사용하여도 상관없다.
Event 객체 속성 종류
📌 Event.target
실제로 이벤트를 발생시킨 요소를 가리킨다. 버블링에 인해서 쌩뚱맞는 다른 타겟이 인식 될 수 있다.
📌 Event.currentTarget
코드에서 이벤트에 바인딩된 DOM 요소를 가리킨다. 즉, addEventListener 앞에 기술된 객체를 가리킨다. 따라서 이벤트 핸들러 함수 내에서 currentTarget과 this는 언제나 일치한다.
예를들어 아래 코드에서 div를 이벤트리스너로 등록했다. 그런데 div안에 button이 있는데, div영역을 click하든 button영역을 click하든 똑같이 이벤트가 발생하게된다.
- this : div
- e.target : button
- e.currentTarget : div
<div>
<button>배경색 변경</button>
</div>
document.querySelector('div').addEventListener('click', function() {
// 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);
});
📌 Event.type
발생한 이벤트의 종류를 나타내는 문자열을 반환한다.
📌 Event.cancelable
요소의 기본 동작을 취소시킬 수 있는지 여부(true/false)를 나타낸다.
📌 Event.eventPhase
이벤트 흐름(event flow) 상에서 어느 단계(event phase)에 있는지를 반환한다.
반환값 | 의미 |
0 | 이벤트 없음 |
1 | 캡쳐링 단계 |
2 | 타깃 |
3 | 버블링 단계 |
📌 Event Delegation (이벤트 위임)
일반적으로 모든 li 요소가 클릭 이벤트에 반응하는 처리를 구현하고 싶은 경우, li 요소에 이벤트 핸들러를 바인딩하면 총 6개의 이벤트 핸들러를 바인딩하여야 한다.
<ul id="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>
function printId() {
console.log(this.id);
}
document.querySelector('#post-1').addEventListener('click', printId);
document.querySelector('#post-2').addEventListener('click', printId);
document.querySelector('#post-3').addEventListener('click', printId);
document.querySelector('#post-4').addEventListener('click', printId);
document.querySelector('#post-5').addEventListener('click', printId);
document.querySelector('#post-6').addEventListener('click', printId);
만일 li 요소가 100개라면 100개의 이벤트 핸들러를 바인딩하여야 한다.
이는 실행 속도 저하의 원인이 될 뿐 아니라 코드 또한 매우 길어지며 작성 또한 불편하다. 그리고 동적으로 li 요소가 추가되는 경우, 아직 추가되지 않은 요소는 DOM에 존재하지 않으므로 이벤트 핸들러를 바인딩할 수 없다.
이러한 경우 이벤트 위임(Event Delegation)을 사용한다.
이벤트 위임은 다수의 자식 요소에 각각 이벤트 핸들러를 바인딩하는 대신 하나의 부모 요소에 이벤트 핸들러를 바인딩하는 방법이다.
위의 경우 6개의 자식 요소에 각각 이벤트 핸들러를 바인딩하는 것 대신 부모 요소(ul#post-list)에 이벤트 핸들러를 바인딩하는 것이다. 또한 DOM 트리에 새로운 li 요소를 추가하더라도 이벤트 처리는 부모 요소인 ul 요소에 위임되었기 때문에 새로운 요소에 이벤트를 핸들러를 다시 바인딩할 필요가 없다. 이는 이벤트가 이벤트 흐름에 의해 이벤트를 발생시킨 요소의 부모 요소에도 영향(버블링)을 미치기 때문에 가능한 것이다.
실제로 이벤트를 발생시킨 요소를 알아내기 위해서는 Event.target을 사용한다.
const msg = document.querySelector('.msg');
const list = document.querySelector('ul#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!';
}
});
# 참고자료
https://opentutorials.org/course/1375/6759
https://poiemaweb.com/js-event
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.