<!-- 설명을 위한 구조 간략화 -->
<!-- 최상위는 document -->
<html>
<body>
<div class="A1"> <!-- royalblue -->
<div class="B1"> <!-- red -->
<div class="C1"></div> <!-- orange -->
</div>
</div>
</body>
</html>
화면은 아래와 같다.
이벤트 흐름은 크게 3가지가 있다.
1. 캡쳐링 단계
2. 타겟 단계
3. 버블링 단계
만약
.B1
을 클릭해서 클릭 이벤트를 발생시켰다면, 최상위 요소부터 이벤트 흐름이 발생하여 내려가는데,document, html, body, A1
까지가 캡쳐링 단계이고, 클릭한B1
은 타겟 단계, 다시 최상위 까지 버블링 단계로A1, body, html, document
순으로 이벤트가 흘러간다.
일반적으로 이벤트 리스너는
타겟 단계
와버블링 단계
에 붙일 수 있는데,캡쳐링
단계에 이벤트를 등록하고 싶다면 이벤트 리스너의 3번째 인자에true
값을 넣어주면 된다.
아래와 같이 이벤트를 등록하고, 화면에서 빨간색 B1을 클릭하면 콘솔에는 어떤 결과가 나올까?
const A1 = document.querySelector('.A1')
A1.addEventListener('click', function(e) {
console.log('A');
})
const B1 = document.querySelector('.B1')
B1.addEventListener('click', function(e) {
console.log('B');
})
결과는, 클릭한 B1
이 먼저, 부모 요소인 A1
이 나중에 콘솔이 찍혔다.
// B
// A
클릭한 타깃은 B1
인데 이벤트 버블링으로 인해 부모 요소쪽으로 이벤트가 흘러서 A1
요소에 등록되어있던 이벤트 리스너가 이벤트 흐름 순서대로 나중에 콘솔이 찍힌 것이다.
실무에서 자식 요소와 부모 요소에 각기 다른 이벤트를 걸어서 사용을 해야할 때가 있는데, 이와 같은 상황이 되면 머리가 아파온다.
내가 클릭한 요소의 이벤트만 작동 시키고 싶은데 부모 요소의 이벤트까지 같이 작동되버리니까 말이다.
이럴 때 사용하는 것이 이벤트 흐름을 차단 시키는 Event.stopPropagation()
이다.
const A1 = document.querySelector('.A1')
A1.addEventListener('click', function(e) {
console.log('A');
})
const B1 = document.querySelector('.B1')
B1.addEventListener('click', function(e) {
e.stopPropagation()
console.log('B');
})
위와 같이 B1
에 등록한 이벤트 리스너(타깃, 버블링단계에 등록한) 매개변수로 넘어오는, 이벤트 객체의 메서드인 stopPropagation()
을 호출하면 이후의 이벤트 흐름(버블링
)을 막을 수 있다.
동일하게 B1
을 클릭해도 A1
쪽에 등록한 이벤트 리스너가 작동하지 않는다.
// B
반대로 이벤트 캡쳐링은 실무에서 쓸 일이 그렇게 많지는 않을거 같다.
타깃 요소의 이벤트 실행 전에 부묘 요소에 있는 이벤트를 먼저 실행시켜야 할 경우, 부모 요소의 이벤트를 캡쳐링 단계에 등록한다.
const A1 = document.querySelector('.A1')
A1.addEventListener('click', function(e) {
console.log('A');
}, true) // 3번째 인자에 true 값을 넣어 캡쳐링 단계에 이벤트를 등록한다.
const B1 = document.querySelector('.B1')
B1.addEventListener('click', function(e) {
console.log('B');
})
B1
을 클릭하면 A1
(캡쳐링 단계)에 등록되어 있던 이벤트가 먼저 실행된다.
// A
// B
currentTarget
은 이벤트가 발생 했을 때, 이벤트가 흘러가는 모든 요소가 각각 currentTarget이 된다.
마찬가지로 이벤트 객체의 프로퍼티로 확인할 수 있다.
target
은 이벤트를 발생 시킨 대상 요소이다.
이벤트가 발생되면 document
객체로부터 이벤트 흐름이 시작되는데 시작하자마자 모든 정보를 담고 이벤트가 흐르기 시작한다.
정말 그럴까? 확인해보자.
B1
을 클릭했을 때 이벤트 흐름이 아직 B1
에 닿지도 않았을 A1
요소에 캡쳐링으로 target
과 currentTarget
을 확인해보자.
const A1 = document.querySelector('.A1')
A1.addEventListener('click', function(e) {
console.log(e.currentTarget);
console.log(e.target);
console.log('A');
}, true) // 3번째 인자에 true 값을 넣어 캡쳐링 단계에 이벤트를 등록한다.
const B1 = document.querySelector('.B1')
B1.addEventListener('click', function(e) {
console.log('B');
})
// console
// <div class="A1">...</div>
// <div class="B1">...</div>
// A
// B
결과는 위와 같이 A1 요소에서도 target의 정보를 확인할 수 있다.
이러한 currentTarget
과 target
정보를 활용하여, 자식 요소인 B1, C1
을 클릭 했을 때는 A1
요소에 등록한 이벤트가 작동하지 않고, 화면에서 파란색으로 보이는 A1 영역
만 클릭 했을 때 이벤트가 발생하도록 할 수 있다.
const A1 = document.querySelector('.A1')
A1.addEventListener('click', function(e) {
if (e.target === e.currentTarget) {
console.log('A');
}
})
이렇게 코드를 짜놓으면 화면에서 눈으로 보이는 A1
요소의 파란색 영역을 클릭 했을 때만 콘솔 ‘A’가 찍히게 된다.