- 캡처링 단계 – 이벤트가 하위 요소로 전파되는 단계
- 타깃 단계 – 이벤트가 실제 타깃 요소에 전달되는 단계
- 버블링 단계 – 이벤트가 상위 요소로 전파되는 단계
예시
테이블 안의 <td>
를 클릭하면 어떻게 이벤트가 흐르는가?
→ <td>
를 클릭하면 이벤트가 최상위 조상에서 시작해 아래로 전파되고(캡처링 단계), 이벤트가 타깃 요소에 도착해 실행된 후(타깃 단계), 다시 위로 전파됩니다(버블링 단계).
한 요소에 이벤트가 발생 → 이 요소에 할당된 핸들러가 동작 → 부모 요소의 핸들러가 동작함.
가장 최상단의 조상 요소(몇 이벤트는 window
객체 그 외에는 document
객체)를 만날 때까지 이 과정이 반복되면서 요소 각각에 할당된 핸들러가 동작함.
‘거의’ 모든 이벤트는 버블링 됨. (버블링 되지 않는 이벤트도(focus 이벤트 등) 존재)
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
https://run.plnkr.co/preview/cl6d6z5zz00033b70hmog5bgg/
동작 원리
가장 안쪽의 <p>
를 클릭 시
<p>
에 할당된 onclick
핸들러가 동작.
바깥의 <div>
에 할당된 핸들러가 동작.
그 바깥의 <form>
에 할당된 핸들러가 동작.
document
객체를 만날 때까지, 각 요소에 할당된 onclick
핸들러가 동작.
동작 결과
<p>
요소 클릭 시, p
→ div
→ form
순서로 3개의 alert 창 출현.
타깃(target) 요소
이벤트가 발생한 가장 안쪽의 요소
event.target
으로 접근 가능부모 요소의 핸들러는 자세한 정보 얻기 가능.
(이벤트가 정확히 어디서 발생했는지 등)
event.target | this (=event.currentTarget) | |
---|---|---|
설명 | 실제 이벤트가 시작된 ‘타깃’ 요소 | ‘현재’ 요소 |
특징 | 버블링이 진행되어도 안 변함. | 현재 실행 중인 핸들러가 할당된 요소를 참조 |
예시
상황
핸들러는 form.onclick
하나밖에 없지만 이 핸들러에서 폼 안의 모든 요소에서 발생하는 클릭 이벤트를 ‘잡아내고(catch)’ 있음.
이유
클릭 이벤트가 어디서 발생했든 상관없이 <form>
요소까지 이벤트가 버블링 되어 핸들러를 실행시키기 때문
form.onclick
핸들러 내의 this
와 event.target
this (event.currentTarget) | event.target | |
---|---|---|
가리키는 요소 | 요소 (이유: 요소에 있는 핸들러가 동작해서) | 폼 안쪽에 실제 클릭한 요소 |
<form>
요소 클릭 시
event.target
=== this
event.stopPropagation()
활용
핸들러에게 이벤트를 완전히 처리한 뒤, 버블링을 중단하도록 명령 가능
예시
<button>
을 클릭해도 body.onclick
은 동작 x
꼭 필요한 경우를 제외하곤 버블링을 막지 말 것
버블링은 유용함.
버블링을 꼭 멈춰야 하는 명백한 상황이 아니라면 버블링을 막지 말 것.
대안 有 (커스텀 이벤트 등을 사용)
자세한 이유 참고 : 링크
event.stopImmediatePropagation()
버블링을 멈추고, 요소에 할당된 다른 핸들러의 동작도 막으려면 사용
이 메서드를 사용하면 요소에 할당된 특정 이벤트를 처리하는 핸들러 모두가 동작 x
한 요소의 특정 이벤트를 처리하는 핸들러가 여러 개인 상황에서, 핸들러 중 하나가 버블링을 멈추더라도 나머지 핸들러는 여전히 동작
event.stopPropagation()
은 위쪽으로 일어나는 버블링은 막아주지만, 다른 핸들러들이 동작하는 건 못 막음.
캡처링 단계를 이용해야 하는 경우는 흔치 x
on<event>
프로퍼티, HTML 속성, addEventListener(event, handler)
를 이용해 할당된 핸들러
→ 캡처링에 대해 전혀 알 수 x
2, 3 번째 단계의 이벤트 흐름(타깃 단계와 버블링 단계)에서만 동작
캡처링 단계에서 이벤트를 잡아내려면
addEventListener
의 capture
옵션을 true
로 설정
elem.addEventListener(..., {capture: true})
// 아니면, 아래 같이 {capture: true} 대신, true를 써줘도 됩니다.
elem.addEventListener(..., true)
capture
옵션
__이면 핸들러는 __ 단계에서 동작.
false
→ 버블링true
→ 캡처링
이벤트가 실제 타깃 요소에 전달되는 단계인 ‘타깃 단계’(두 번째 단계)는 별도로 처리되지 x.
캡처링과 버블링 단계의 핸들러는 타깃 단계에서 트리거 됨.
문서 내 요소 '전체’에 핸들러를 할당해서 어떤 핸들러가 동작하는지
<p>
클릭 시, 이벤트 전달 순서
HTML
→ BODY
→ FORM
→ DIV
(캡처링 단계, 첫 번째 리스너)
P
(타깃 단계, 캡쳐링과 버블링 둘 다에 리스너를 설정했기 때문에 두 번 호출됩니다.)
DIV
→ FORM
→ BODY
→ HTML
(버블링 단계, 두 번째 리스너)
event.eventPhase
현재 발생 중인 이벤트 흐름의 단계 파악 가능
방법
반환되는 정숫값에 따라 이벤트 흐름의 현재 실행 단계를 구분
이 프로퍼티는 자주 사용 되지 x.
이유: 핸들러를 통해 흐름 단계 파악 가능
핸들러를 제거 시, removeEventListener
가 같은 단계에 있어야 함.
addEventListener(..., true)
로 핸들러를 할당해 줬다면, 핸들러를 지울 때, removeEventListener(..., true)
를 사용해 지워야 함.
같은 단계에 있어야 핸들러가 지워집니다.
같은 요소와 같은 단계에 설정한 리스너는 설정한 순서대로 동작
특정 요소에 addEventListener
를 사용해 한 단계에 이벤트 핸들러를 여러 개 설정했다면 이 핸들러들은 설정한 순서대로 동작
elem.addEventListener("click", e => alert(1)); // 첫 번째로 트리거됨.
elem.addEventListener("click", e => alert(2));
e.preventDefault()
참고