Event를 공부하면서 버블링(Bubbling)과 캡처링(Capturing)에 대한 개념이 나와서 정리를 해보기로 했다.
window로부터 이벤트가 발생한 요소까지 이벤트를 전파한다. 즉, 이벤트가 하위 요소로 전파되는 단계다. 실제 코드에서 자주 쓰이지는 않지만 개념적으로만 알고있으면 될 것 같다.
이벤트가 발생한 요소부터 window까지 이벤트를 전파한다. 한 요소에 이벤트가 발생하면, 이 요소에 할당된 핸들러가 동작하고 이어서 부모 요소의 핸들러가 동작한다. 가장 최상단의 조상 요소를 만날 때까지 이 과정이 반복되면서 요소 각각에 할당된 핸들러가 동작한다.
FORM > DIV > P 형태로 중첩된 구조가 있고, 요소 각각에 핸들러가 할당되어 있다.
<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>
가장 안쪽의 <p>
를 클릭하면 순서대로 다음과 현상이 나타난다.
<p>
에 할당된 onclick 핸들러가 동작.<div>
에 할당된 핸들러가 동작.<form>
에 할당된 핸들러가 동작.document 객체를 만날 때까지, 각 요소에 할당된 onclick 핸들러가 동작한다.
거의 모든 이벤트는 버블링이 된다.
여기서 키워드는 '거의'이다. Focus 이벤트와 같은 버블링이 되지 않는 이벤트도 있다.
이벤트가 정확히 어디서 발생했는지 등에 대한 정보를 얻을 수 있다. 이벤트가 발생한 가장 안쪽의 요소는 target요소라고 불리고, event.target
을 사용해 접근할 수 있다.
event.target
은 실제 이벤트가 시작된 '타깃'요소이다. 버블링이 진행되어도 변하지 않는다.event.currentTarget
은 현재 요소로 현재 실행 중인 핸들러가 할당된 요소를 참조한다.<body>
<div class="outer">
<button>Click Me</button>
</div>
<script>
const outer = document.querySelector('.outer');![]
const button = document.querySelector('button');
outer.addEventListener('click', event => {
console.log(`outer: ${event.currentTarget}, ${event.target}`);
});
button.addEventListener('click', event => {
console.log(`button1 ${event.currentTarget}, ${event.target}`);
});
</script>
</body>
위에 코드에서 button
을 클릭하면 console에 아래와 같이 나온다.
여기서 event.target 즉, 이벤트가 시작된 타깃요소는 HTMLButtonElement가 되고, event.currentTarget는 .outer
의 HTMLDivElement와 HTMLButtonElement가 나타나게 된다.
기본적으로 알고 있는 방법이 이벤트 객체의 메서드인 event.stopPropagation()를 사용하는 방법이다.
위에 코드의 button에 event.stopPropagation()를 추가하게 되면 button에서만 핸들링이 일어나고 버블링이 일어나지 않는다.
button.addEventListener('click', event => {
console.log(`button1 ${event.currentTarget}, ${event.target}`);
event.stopPropagation()
});
event.stopPropagation()은 위쪽으로 일어나는 버블링은 막아주지만, 다른 핸들러들이 동작하는 건 막지 못 한다.
button.addEventListener('click', event => {
console.log(`button ${event.currentTarget}, ${event.target}`);
event.stopPropagation()
});
button.addEventListener('click', event => {
console.log(`button2 ${event.currentTarget}, ${event.target}`);
});
위에 코드처럼 같은 button에 적용된 핸들러가 있을 경우, event.stopPropagation()만으로는 동작을 멈추지 못 한다. 이때 사용되는 것이 event.stopImmediatePropagation()다. 이 메서드를 사용하면 버블링을 멈추고, 요소에 할당된 다른 핸들러의 동작도 막아준다.
하지만!! event.stopPropagation()와 event.stopImmediatePropagation()의 사용은 위험하다.
내가 마음대로 이벤트를 취소해버리면 다른 부분에서 이 이벤트를 통해 더 의미 있는 일을 할 수도 있고 다른 기능을 추가해놨을 수도 있기 때문이다. 큰규모의 프로젝트 등에서 이것으로 인한 오류가 발행 할 수도 있다. 따라서 조건문을 이용하여 버블링을 막아주는 방법을 사용하면 좋다.
outer.addEventListener('click', event => {
if (event.target !== event.currentTarget) {
return;
}
});
위에 코드처럼 event.target와 event.currentTarget을 이용하여 조건문을 만들어주면 된다.