이벤트란 웹페이지가 사용자와 상호작용하면서 발생하는 동작들을 의미한다. (클릭, 마우스 이동 ...) 즉, 이벤트가 발생했다는 것은 웹 페이지에서 특정 동작이 발생하여, 웹 브라우저가 그 사실을 알려주는 것을 의미한다.
이벤트 핸들러는 이벤트를 처리하기 위한 함수와 연결시켜주는 역할을 한다.
이벤트 핸들러는 때때로 이벤트 리스너(event listener)라고 불린다. 엄밀히 말하자면, 같은 동작을 하기 때문이다.
event.type
: 이벤트 타입
event.currentTarget
: 이벤트를 처리하는 요소. 화살표 함수를 사용해 핸들러를 만들거나 다른 곳에 바인딩하지 않은 경우엔 this가 가리키는 값과 같음
event.clientX
/ event.clientY
: 커서의 상대 좌표
① HTML 속성: onclick="..."
② DOM 프로퍼티: elem.onclick = function
③ 메서드: elem.addEventListener(이벤트명, 실행할이벤트함수, [options])
[options]
- once
: true면 이벤트가 트리거될 때 리스너가 자동으로 삭제
- capture
: 캡쳐링 여부 (false이면 버블링, true이면 캡쳐링)
- passive
: true면 리스너에서 지정한 함수가 preventDefault()를 호출하지 않음.
- 인터넷 익스프롤러에 이벤트 추가 - attachEvent()
대상객체.attachEvent(이벤트명, 실행할이벤트함수)
// 아이디가 button인 요소를 선택하여 변수 button에 지정
var button = document.getElementById('button');
// 이벤트가 발생하면 실행할 함수
function clickShow () {
console.log("click");
}
// 이벤트 핸들러 연결
button.addEventListener('click', clickShow);
onclick은 웹 초창기부터 지원하던 기능, 모든 브라우저와 버전에서 호환
addEventListener는 인터넷 익스프롤러 6,7,8에서는 호환 X
addEventListener가 버블링, 캡처링 처리에는 편리함
onclick은 한 개의 이벤트 리스너만 지정 가능 (두 번 이상 사용하면 덮어쓰기 됨)
addEventListener는 여러 개의 이벤트 리스너 추가 가능
결론적으로 onclick보다는 addEventListener를 사용하는 것이 모던한 방식이다.
- elem.onclick = null
- removeEventListener()
대상객체.removeEventListener(이벤트명, 실행할이벤트함수, [options])
- 인터넷 익스프롤러 이벤트 삭제 - detachEvent()
대상객체.detachEvent(이벤트명, 실행할이벤트함수)
① 이벤트의 대상이 되는 객체나 요소에 프로퍼티로 등록한 이벤트 리스너를 가장 먼저 호출
② 그 후 addEventListener() 메소드를 사용하여 등록한 이벤트 리스너를 등록한 순서대로 호출
이벤트가 발생하면 이벤트가 발생한 가장 안쪽 요소가 타깃(target) 요소가 되며, event.target을 사용해 접근할 수 있다.
event.target과 this(event.currentTarget)의 차이
event.target은 실제 이벤트가 시작된 ‘타깃’ 요소이기 때문에 버블링이 진행되어도 변하지 않는다.
this는 ‘현재’ 요소로, 현재 실행 중인 핸들러가 할당된 요소를 참조한다.
만약, form 안에 있는 p를 클릭하여 이벤트가 발생했을 경우
event.target은 폼 안쪽에 실제 클릭한 요소인 p를 가리키지만, this는 form 요소에 있는 핸들러가 동작했기 때문에 form 요소를 가리킨다.
p가 아니라 form을 클릭한다면, event.target과 this가 form으로 같은 것을 가리킨다.
- 핸들러가 onclick을 사용해 할당되었다면 onclick="return false"
- 핸들러가 addEventListener를 사용해 할당되었다면 preventDefault()
표준 DOM 이벤트에서 정의한 이벤트 흐름의 3가지 단계
- 버블링 – 이벤트가 상위 요소로 전파되는 단계
- 캡처링 – 이벤트가 하위 요소로 전파되는 단계
- 타깃 – 이벤트가 실제 타깃 요소에 전달되는 단계
이벤트가 발생한 요소부터 시작해서, DOM 트리를 따라 위쪽으로 올라가며 전파되는 방식이다. (웹페이지 내부의 버튼을 클릭했을 때 버튼을 감싸고 있는 부모 태그들 또한 클릭 이벤트에 반응하는 것)
해당 요소의 리스너가 실행된 후, 그 부모 요소에 등록된 리스너가 실행되고, 또다시 그 부모 요소에 등록된 리스너가 실행된다. 이러한 이벤트의 전파는 Document 객체뿐만 아니라 가장 마지막에는 Window 객체까지 계속 이어진다. 이렇게 하나의 태그를 클릭해도 여러 개의 이벤트가 발생하는 이유는 브라우저가 이벤트를 감지하는 방식 때문이다.
이벤트가 발생한 요소까지 DOM 트리의 최상위부터 아래쪽으로 내려가면 전파되는 방식이다.
이 전파 방식은 맨 먼저 Window 객체의 리스너가 실행된 후, Document 객체에 등록된 리스너가 실행되고, 또다시 그 자식 요소에 등록된 리스너가 실행된다. 이러한 이벤트의 전파는 이벤트가 발생한 요소까지 이어진다.
이 전파 방식을 사용하기 위해서는 addEventListener() 메소드의 세 번째 인수에 true를 전달하면 된다. 기본적으로 addEventListener의 세 번째 요소를 작성하지 않으면 default 값이 false인데, false로 설정할 경우, 핸들러는 버블링으로 설정된다.
document.getElementById("click").addEventListener("click", clickShow, true);
form, div, p {
color: white;
border-radius: 50px;
padding: 20px;
cursor: pointer;
}
form {
background: darkblue;
}
div {
margin: 10px;
background: #fbc02d;
}
p {
margin: 10px;
background: #f57f17;
}
<form>
FORM
<div>
DIV
<p>P</p>
</div>
</form>
<script>
for(let elem of document.querySelectorAll('*')) {
elem.addEventListener("click", e => alert(`버블링: ${elem.tagName}`));
elem.addEventListener("click", e => alert(`캡쳐링: ${elem.tagName}`), true);
}
</script>
(1) addEventListener의 세 번째 요소를 작성하지 않았을 때는 default 값이 false이기 때문에 핸들러가 버블링으로 설정되어 동작한다.
버블링 방식은 가장 안쪽의 p 요소를 클릭하면 P -> DIV -> FORM -> BODY -> HTML
순서로 알림창이 뜬다. 이처럼 이벤트 버블링은 document 객체를 만날 때까지, p를 감싸고 있는 부모 요소에 할당된 onclick 핸들러가 동작한다.
(2) addEventListener의 세 번째 요소를 true로 작성했을 때는 핸들러가 캡쳐링으로 설정되어 동작한다.
캡쳐링 방식은 가장 안쪽의 p 요소를 클릭하면 HTML -> BODY -> FORM -> DIV -> P
순서로 알림창이 뜬다. 이처럼 이벤트 캡처링은 p요소를 만날 때까지, p를 감싸고 있는 부모 요소에 할당된 onclick 핸들러가 동작한다.
- stopPropagation()
대상객체.stopPropagation();
다음과 같이 p태그에 stopPropagation()을 사용하면 p태그를 클릭해도 이벤트가 발생하지 않는다. 이벤트 버블링이 작동하지 않기 때문이다.
<form onclick="alert('form')">
FORM
<div onclick="alert('div')">
DIV
<p onclick="event.stopPropagation()">P</p>
</div>
</form>
혹은 어떠한 태그를 클릭해도 이벤트 버블링이 발생하지 않도록 할 수도 있다.
for (let elem of document.querySelectorAll('*')) {
elem.addEventListener("click", e => alertEvent(e, elem.tagName));
}
function alertEvent(event, _tagName) {
event.stopPropagation();
alert(_tagName);
}
이벤트 위임(event delegation)은 비슷한 이벤트 패턴으로 여러 요소를 다뤄야 할 때 사용된다. 이벤트 위임을 사용하면 요소마다 핸들러를 할당하지 않고, 요소의 공통 조상에 이벤트 핸들러를 단 하나만 할당해도 여러 요소를 한꺼번에 다룰 수 있습니다. 이벤트 위임은 캡처링과 버블링을 활용하여 구현할 수 있다.
* 이벤트 위임을 사용하려면 이벤트가 반드시 버블링 되어야 한다.
(1) Hide messages with delegation
container.onclick = function(event) {
let target = event.target; // 이벤트 타깃 요소
if (target.className != 'remove-button') return;
// 이벤트가 발생한 타깃이 remove-button이 아니라면 아무것도 x
let pane = event.target.closest('.pane');
pane.remove();
// 맞다면 pane 삭제
};
(2) 왜 'return false'가 작동하지 않을까요
<script>
function handler(event) {
alert("...");
event.preventDefault();
}
</script>
<a href="https://w3.org" onclick="handler(event)">w3.org</a>
참고자료
🔗 이벤트 기초
🔗 이벤트의 개념
🔗 이벤트 버블링과 이벤트 캡처링