[JS] 이벤트, 이벤트 처리기
이 문서에서는 DOM이란 개념이 나오니, 먼저 JS - 문서 객체 모델 (Document Object Model) 페이지를 읽고 오는 것을 권장드립니다.
이벤트 (Event)
웹 페이지에서의 이벤트란, 웹 페이지 내 영역에 대해서 사용자 또는 웹 브라우저가 가하는 동작을 말한다. 마우스 클릭, 키보드 입력 등이 이벤트에 포함된다. 주의할 점은 웹 페이지 외 영역에서의 마우스 클릭 등의 동작은 이벤트로 보지 않는다. 브라우저 맨 위의 주소창에 텍스트를 입력한다든가 브라우저 설정, 또는 탭 등을 누르는 행위도 웹 페이지 외부의 영역에서 일어난 일이기에 이벤트가 아니다.
다음은 웹 페이지에서 일어날 수 있는 이벤트 종류와 세부 이벤트 목록이다. 다음에 언급될 이벤트 말고도 더 많은 이벤트가 많다고 한다. 더 많은 이벤트는 [4] 참고.
마우스 이벤트
- click : 사용자가 HTML 요소를 클릭할 때 이벤트 발생.
- dblclick : 사용자가 HTML 요소를 더블클릭했을 때 이벤트 발생.
- mousedown : HTML 요소 위에서 마우스 버튼을 눌렀을 때 이벤트 발생.
- mousemove : HTML 요소 위에서 마우스 커서를 움직였을 때 이벤트 발생.
- mouseover : HTML 요소 위로 마우스 커서가 올려졌을 때 이벤트 발생.
- mouseout : HTML 요소에서 마우스 커서가 벗어날 떄 이벤트 발생.
- mouseup : HTML 요소 위에 놓인 마우스 버튼을 뗐을 때 이벤트 발생.
키보드 이벤트
- keydown : 키를 누르는 동안에 이벤트 발생.
- keypress : 키를 누를 때 이벤트 발생.
- keyup : 눌러진 키를 뗐을 때 이벤트 발생.
페이지 로딩 이벤트
- abort : 웹 페이지 로딩 완료 전에 로딩이 중단되었을 때 이벤트 발생.
- error : 웹 페이지가 정확한 로딩에 실패했을 때 이벤트 발생.
- load : 웹 페이지 로딩 완료 시 이벤트 발생.
- resize : 웹 페이지 화면 크기 변경 시 이벤트 발생.
- scroll : 웹 페이지 화면이 스크롤될 때 이벤트 발생.
- unload : 웹 페이지에서 벗어날 때 이벤트 발생.
폼 이벤트
여기서의 폼은 앞서 HTML 태그들에 대해 살펴보았을 때 <button>
, <form>
등 사용자가 정보를 입력할 수 있는 폼 요소들을 의미한다.
- blur : 폼 요소가 포커스 해제되었을 때 이벤트 발생.
- change :
<input>
,<select>
,<textarea>
태그에서 사용되며, 목록, 체크 여부 등 어떠한 상태가 변경될 때 이벤트 발생. - focus : 폼 요소가 포커스 되었을 때 이벤트 발생.
<label>
,<select>
,<textarea>
,<button>
태그에서 사용됨. - reset : 폼이 리셋될 경우 이벤트 발생.
- submit : submit 버튼 클릭 시 이벤트 발생.
Event Handler
어떠한 이벤트가 일어났을 때, 그에 상응되는 어떤 기능이 동작되도록 하여 해당 이벤트를 처리하게 하는 함수를 이벤트 처리기(Event handler, Event listener라고도 한다)라 한다.
이벤트는 웹 페이지 상에 존재하는 HTML 태그로 생성되는 요소들에서 발생하기에, 이벤트 핸들러로 이벤트를 처리하기 위해선 해당 이벤트가 발생할 HTML 태그를 불러와 지정한 후, 해당 이벤트를 이벤트 핸들러와 연결해야 한다. 이에 대한 방법으로 다음이 있다.
- HTML 태그 내에 on + ‘이벤트명’을 합친 속성에 이벤트를 처리할 함수명을 적는다. 그리고 이벤트를 처리할 함수는 자바스크립트 코드로 정의한다.
<div id="container" onclick="changeBgColor('blue')"></div>
<script>
function changeBgColor(color) {
let selector = document.querySelector('#container');
selector.style.backgroundColor = color;
}
</script>
위 예제는 id=’container’인 div 요소를 클릭하는 이벤트가 발생하면 해당 요소의 배경색을 파란색으로 바꿔주는 기능을 가진 함수를 연결한 예제이다.
- 자바스크립트 내부에서 HTML 요소를 DOM을 통해 선택한 후, 해당 DOM 요소에서 .on + 이벤트명 = 함수명; 과 같이 작성하면 된다.
<div id="container"></div>
<script>
let selector = document.querySelector('#container');
selector.onclick = changeBgColor;
function changeBgColor() {
selector.style.backgroundColor = "blue";
}
</script>
위 예제는 예제 1-1과 실행결과가 동일하다. 단, on + 이벤트명의 속성값에는 함수명만 오고 그 뒤에 괄호는 붙이지 않는다.
이 방법은 HTML와 자바스크립트 코드를 구분해주므로 유지보수에 유리하다.
위 두 방법의 공통점은, 어떠한 웹 요소를 선택할 때, document.querySelector()
함수를 이용한다는 것이다. 해당 함수 인자로는 css에서 사용하던 id, class, 태그 선택자 모두 대입할 수 있다.
- addEventListener() 메서드 사용하기
앞선 방법들과 달리, DOM 요소에 addEventListener()
메서드를 사용하면 한 번에 여러 개의 이벤트 핸들러들을 연결시킬 수 있다. 해당 메서드의 구조는 다음과 같다.
DOMElement.addEventListener(event, function, capture);
- event : event type을 지정한다. 앞서 언급했던 여러 이벤트명들 중 원하는 이벤트명을 문자열 형태로 기입하면된다. 단, “onclick”에서 ‘on’을 뺀 ‘click’과 같이 이벤트명만 입력한다.
- function : 해당 요소에서 지정된 이벤트 발생 시 이 이벤트를 처리할 이벤트 핸들러 함수명을 입력한다. 해당 함수의 첫 번째 인자는 event 객체를 인수로 받는다. (event 객체에 대해선 후술)
- capture : 이벤트를 캡쳐할 것인지 여부를 지정. boolean 값을 입력할 수 있으며, 기본값은 false이다. true : 이벤트 캡처링. DOM tree에서 부모 노드에서 자식 노드로 이벤트를 전파시킨다. false : 이벤트 버블링. DOM tree에서 자식 노드에서 부모 노드로 이벤트를 전파시킨다. 기본값. (이벤트 캡처링과 버블링은 추후 후술)
다음은 위 메서드를 이용하여 이미지를 클릭하면 이미지 주위의 배경색이 변경됨과 동시에 설명글이 뜨게끔 하는 예제이다. 각각 HTML, CSS, JS 파일로 나눠서 작성하였다. addEventListener()
메서드를 이용하면 다른 방법들과 달리 하나의 요소에 여러 개의 이벤트 핸들러를 연결할 수 있다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="addEventListener.css">
</head>
<body>
<div id="container">
<div id="image">
<img src="..\images\coffee.png" width="300" height="300">
</div>
<article>
<p>
커피는 전 세계에서 가장 사랑받는 음료 중 하나입니다.
이것은 커피나무의 열매에서 추출한 커피 콩을 가공하여 만듭니다.
원산지는 에티오피아라고 알려져 있으며, 15세기에
아라비아 반도를 통해 전 세계로 퍼져나갔습니다.
</p>
<p>
커피는 다양한 방식으로 준비하고 즐길 수 있습니다.
에스프레소, 아메리카노, 카푸치노, 라떼 등의 종류가 있으며,
각각의 방식에 따라 맛과 향이 다르게 느껴집니다.
블랙 커피에서부터 설탕, 우유, 휘핑크림 등을 첨가하여
다양한 맛의 커피를 만들 수 있습니다.
</p>
<p>
커피는 카페인이라는 자극제를 함유하고 있어,
피로를 줄이고 집중력을 높이는 효과가 있습니다.
그래서 많은 사람들이 아침에 커피를 마시거나,
일 중에 피로를 풀기 위해 커피를 선호합니다.
하지만 과다섭취는 수면 장애나 심장 박동수 증가 등
부작용을 유발할 수 있으므로 적절히 섭취해야 합니다.
</p>
<p>
또한, 커피는 사회적인 의미도 가지고 있습니다.
사람들은 커피를 마시며 이야기를 나누고,
친구나 사랑하는 사람과의 시간을 보내곤 합니다.
커피숍은 이러 사회적 교류의 장소로서의 역할을 하며,
많은 사람들에게 휴식과 여유를 제공합니다.
</p>
<p>
커피는 그 자체로도 많은 매력을 가지고 있지만,
커피와 관련된 다양한 문화와 역사,
사회적 의미를 알게 되면 그 매력은 더욱 커집니다.
이러한 이유로 커피는 오늘날 전 세계 사람들에게 사랑받고 있는 음료입니다.
</p>
</article>
</div>
<script src="addEventListener.js"></script>
</body>
</html>
#container {
width: 90%;
margin: 10px auto;
padding: 10px;
}
#image {
display: flex;
justify-content: center;
margin-bottom: 10px;
}
article {
padding: 10px;
display: none;
}
const imageDiv = document.getElementById('image');
const article = document.querySelector('article');
function getRandomInt(minInt, maxInt) {
/*
minInt, maxInt: 정수
주어진 정수 범위 내에서 무작위로 정수 하나를 추출하여 반환하는 함수.
minInt, maxInt도 범위에 포함된다.
*/
let range = maxInt - minInt + 1;
return Math.floor(Math.random() * range) + minInt;
}
imageDiv.firstElementChild.addEventListener('click', () => {
if (article.style.display == 'none' || article.style.display == '') {
article.style.display = 'block';
} else {
article.style.display = 'none';
}
})
imageDiv.firstElementChild.addEventListener('click', () => {
if (article.style.display != 'none') {
let rgb = [0, 0, 0];
rgb = rgb.map(() => getRandomInt(0, 255));
imageDiv.style.backgroundColor = `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
} else {
imageDiv.style.backgroundColor = 'white';
}
})
예제 1-3~5 실행결과1. 첫 화면
예제 1-3~5 실행결과2. 이미지 클릭 후 화면.
이벤트 버블링과 캡처링
버블링 (bubbling)
우선 다음의 예제를 먼저 보자.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.box {
border: 1px solid black;
margin: 10px;
display: flex;
align-items: center;
justify-content: center;
}
#c {
width: 50px;
height: 50px;
background-color: blueviolet;
}
#b {
width: 100px;
height: 100px;
background-color: burlywood;
}
#a {
width: 150px;
height: 150px;
background-color: cornflowerblue;
}
</style>
</head>
<body>
<div id="container">
<div id="click-boxes" class="box">
<div id="a" class="box">
<p>a</p>
<div id="b" class="box">
<p>b</p>
<div id="c" class="box"><p>c</p></div>
</div>
</div>
</div>
</div>
<script>
const cDiv = document.getElementById('c');
const bDiv = document.getElementById('b');
const aDiv = document.getElementById('a');
function itsme(event) {
alert(`이벤트를 처리한 요소: ${this.id} \n이벤트가 발생한 요소: ${event.target.id}`);
}
cDiv.onclick = itsme;
bDiv.onclick = itsme;
aDiv.onclick = itsme;
</script>
</body>
</html>
예제 2-1 실행결과
위 예제는 a, b, c 박스 중 어느 박스를 마우스로 클릭했을 때, 어떤 박스를 눌렀는지를 알림창으로 알려주는 예제이다. 그런데 이상한 점은, c 박스 내부를 클릭하면 c 박스 뿐만 아니라 b, a 박스도 클릭했다는 알림창이 연속으로 뜬다. b 박스를 클릭하면 b 뿐만 아니라 a 박스도 클릭했다는 알림창이 뜬다. 분명 하나의 박스만 클릭했음에도, 그 바깥에 있는 박스들까지 마우스 클릭 이벤트가 발생한 것으로 처리된다. 왜 이런 현상이 발생할까?
이는, 하위 요소에서 이벤트가 발생하면, 해당 요소에 연결된 이벤트 핸들러로 이벤트가 처리되는 것에서 멈추지 않기 때문이다. 발생한 이벤트가 부모 요소까지 올라가서 해당 부모 요소의 핸들러까지 작동시키는 것이다. 이러한 과정은 최상위 조상 요소에 다다를 때까지 진행된다. 이러한 현상을 이벤트 버블링(bubbling)이라고 하며, 마치 물 속에서 맨 아래에 있던 거품이 위로 올라오는 과정과 같아서 붙여진 이름이다. 거의 대부분의 이벤트가 버블링의 적용 대상이지만, 몇몇 이벤트는 버블링이 되지 않는다고 한다. 이러한 버블링 현상에 의해 위 예제에서는 최하위 요소인 c 요소를 클릭해도 그 이벤트가 한 단계씩 부모 요소로 올라가서 해당 부모 요소들의 이벤트 핸들러도 작동시키기에 벌어진 현상이다.
버블링 중단
만약 상대적으로 하위에 존재하는 요소에서 이벤트 핸들러를 통해 이벤트를 처리한 후에, 버블링 현상이 일어나지 않기를 원한다면, 버블링이 일어나길 원치 않는 요소에 이벤트 객체의 메서드인 event.stopPropagation()
을 이벤트 핸들러로 연결하면 된다.
다음은 앞서 살펴본 예제 2-1의 자바스크립트 코드 부분에서, id가 b인 div 요소에 event.stopPropagation() 메서드를 이벤트 핸들러로 연결하여 버블링을 막은 예제이다.
cDiv.onclick = itsme;
bDiv.addEventListener('onclick', itsme); // 수정됨
bDiv.addEventListener('onclick', event.stopPropagation()); // 추가됨.
aDiv.onclick = itsme;
위 예제로 고친 후 실행해보면, c 박스를 누르더라도 b, a 박스에 대한 알림창까지 뜨진 않는 것을 알 수 있다.
한 편, 특정 요소의 특정 이벤트를 처리하도록 연결된 이벤트 핸들러가 여러 개일 경우(이는 addEventListener()
메서드로 가능하다), event.stopPropagation()
메서드를 쓰더라도, 조상 요소를 향해 전파되는 이벤트는 막아도, 해당 요소의 특정 이벤트에 연결된 다른 “형제” 이벤트 핸들러들의 동작까지 중단시키진 못한다. 만약 이러한 “형제” 이벤트 핸들러들의 동작도 그 즉시 막고자 한다면 event.stopImmediatePropagation()
메서드를 대신 사용하면 된다. 그러면 버블링 중단과 동시에 특정 이벤트에 연결된 다른 이벤트 핸들러들의 동작도 중단된다.
한 편, 현재 이벤트의 기본 동작을 중단하기 위해서는 event.preventDefault()
메서드를 사용하거나 콜백 함수 내에서 return false 문을 사용하면 된다. 한 예로, 버튼을 누르면 기본적으로 submit 기능 (폼 제출 기능)이라는 기본 기능이 실행되며, 이 경우, 사용자 정의 이벤트 핸들러가 의도대로 작동하지 않을 수 있다. 이 경우, preventDefault()
메서드를 이용하여 해당 기본 기능을 중단시킴으로써 사용자 정의 이벤트 핸들러가 작동되도록 할 수 있다.
<!-- 생략 -->
<button class="material-symbols-outlined" id="search-button">search</button>
<!-- 생략 -->
<script>
const button = document.querySelector('#search-button');
// 생략
button.addEventListener('click', (event) => {
// 버튼의 기본 기능인 submit 기능이 기존 콜백 함수를 무시하고
// 실행된다고 하므로, 이를 방지하기 위한 코드.
// 현재 이벤트의 기본 동작을 중단하는 메서드.
// HTML 태그 내 onclick과 같은 속성 사용 시 다음과 같이 처리.
// <div onclick="callbackFunc(); return false;">
event.preventDefault();
if (search.value.trimStart() !== '') {
createSearchHistory(search.value);
search.value = '';
}
});
</script>
캡처링 (capturing)
앞서 버블링에 대해 이해했다면, 캡처링은 매우 이해하기 쉽다. 버블링과 이벤트 전파 방향이 정확히 반대일 때를 캡처링이라고 부르기 때문이다. 즉, 최상위 요소인 document에서부터 시작하여 하위 요소 방향으로 이벤트가 전파되는 것을 캡처링이라 한다. 캡처링으로 이벤트 전파가 흐를 때 이벤트 핸들러가 동작하게끔 하려면 앞서 본 addEventListener()
메서드의 세 번째 인자를 true로 설정하면 된다.
참고로, 실제로는 캡처링을 사용할 기회가 잘 없다고 한다.
event.target
버블링 또는 캡처링에 의하면 간혹 이벤트의 발생 위치와 그 이벤트를 처리하는 핸들러와 연결된 요소의 위치가 다를 수 있다. (앞선 예제 2-1의 자바스크립트 코드 부분에서 맨 아래에 div 요소와 이벤트 핸들러를 연결하는 부분에서 cdiv.onclick… 와 bdiv.onclick… 코드를 주석 처리하면 이를 확실히 확인할 수 있다) 이 때, 이벤트가 발생한 요소와 현재 실행 중인 이벤트 핸들러가 할당된 요소를 각각 알고 싶다면 event 객체의 event.target과 event.currentTarget 프로퍼티를 사용하면 된다.
- event.target : 이벤트가 처음으로 발생한 요소를 담은 event 객체의 프로퍼티. 이 요소를 target 요소라 부른다. 앞선 예제 2-1에서, c 박스를 눌렀을 때
<div id=’c’>
요소가 이에 해당한다. - event.currentTarget (this) : 이벤트를 처리하는 현재 이벤트 핸들러와 연결된 요소를 담은 프로퍼티. this라고 해도 된다. 앞선 예제 2-1에서는 c 박스를 클릭했을 경우, 버블링에 의해 만약 b 요소에서 핸들러가 작동 중이라면 이 경우에는 b 요소가 event.currentTarget (또는 this)에 해당되는 것이다.
addEventListener()
메서드의 콜백 함수로 화살표 함수를 사용하는 경우, 해당 함수 내에 this는 event.currentTarget이 아닌, 전역 객체를 의미하게 된다. 웹 문서의 경우, window 객체로 설정된다고 한다. 따라서 이 경우, this 키워드를 사용하고자 한다면 콜백 함수로 화살표 함수가 아닌 function() {}와 같은 익명 함수를 사용하거나, 아니면 화살표 함수를 콜백 함수로 사용하고자 한다면 this 키워드 대신 event.currentTarget 프로퍼티를 직접 사용해야 한다.
someElement.addEventListener('click', () => {
console.log(this); // window 객체가 출력됨.
});
someElement.addEventListener('click', function() {
console.log(this); // event.currentTarget과 동일함.
});
DOM event flow
표준 문서[5]에 따르면 DOM event 전파 흐름에는 세 단계가 있고, 순서대로 실행된다고 한다.
- 캡처링 단계 : 이벤트가 최상위 요소에서 하위 요소로 전파되는 단계.
- 타깃 단계 : 이벤트가 target 요소에 전달되는 단계.
- 버블링 단계 : 이벤트가 상위 요소로 전파되는 단계.
사진 1. DOM event flow 그림. [5]에서 참조함.
DOM의 event 객체
DOM에는 event 객체가 존재한다. 이 event 객체에는 이벤트 유형에 따라 MouseEvent, KeyboardEvent 등 다양한 종류의 객체들이 존재하며, 각자 이벤트 정보를 프로퍼티로 담고 있다. event 객체의 종류와 각 event 객체의 프로퍼티들에 대한 자세한 사항은 사이트 [1]의 “Event 기반 인터페이스” 챕터 참고.
이벤트 핸들러 함수의 첫 번째 인수로는 발생한 이벤트에 대한 event 객체가 전달되며, 해당 매개변수명을 보통 e, event로 하기도 한다. 이를 통해 핸들러 함수 내에서 해당 이벤트 정보에 접근할 수 있다. 핸들러 함수에서 첫 번째 인자로 이벤트 객체를 받고, 이를 통해 이벤트 정보를 확인하는 것은 앞선 예제 2-1의 “itsme” 핸들러 함수에서 선보인 바 있으니 참고.
마치며…
이 글에서는 이벤트란 무엇이고 이벤트 유형에는 무엇이 있는지 살펴보았다. 또한, 발생한 이벤트를 처리하는 이벤트 핸들러에 대해서도 살펴보았고, 이벤트 흐름에 대해서도 살펴보았다.
HTML 문서 내 어떤 요소에서 이벤트가 발생하였고, 이 이벤트를 처리하기 위한 이벤트 핸들러를 연결하기 위해선 먼저 자바스크립트에서 HTML 요소에 접근할 수 있어야 한다. 이러한 방법을 알려면 우선 DOM이란 개념에 대해 알아야 하고, 요소나 CSS 속성에 어떻게 접근하여 변경할 수 있는지 등을 알아야 하는데, 이에 대한 이야기는 JS - 문서 객체 모델 (Document Object Model) 페이지에서 설명한다.
References
[2] 버블링과 캡처링
[3] 🌐 한눈에 이해하는 이벤트 흐름 제어 (버블링 & 캡처링)
[4] 요소에서 발생할 수 있는 이벤트 지정 시 사용할 수 있는 이벤트 목록.
[5] HTML 표준 사이트 W3C에 의한 DOM event flow
[6] event.target 에 대한 설명
[7] event.currentTarget에 대한 설명.
Event: currentTarget property - Web APIs | MDN
[8]
[JavaScript] JavaScript에서 이벤트 전파를 중단하는 네가지 방법
[9] addEventListener()
내 콜백 함수로 화살표 함수를 사용하는 경우, 화살표 함수 내에서 this 키워드가 event.currentTarget이 아닌 전역 객체(window 객체)로 찍히는 것에 대한 설명글 포함.
[10] 고경희 - Do it! 한 권으로 끝내는 웹 기본 교과서 HTML+CSS+자바스크립트 웹 표준의 정석
This content is licensed under
CC BY-NC 4.0
댓글남기기