안녕하세요! muko 입니다.
오늘은 브라우저에서 이벤트와 관련된 개념인 버블링에 대해서 다뤄보도록 하겠습니다.
버블링 (bubbling)
브라우저에서 한 요소에 이벤트가 발생했을 때 이 요소에 할당된 이벤트 핸들러가 동작하고, 이어서 부모 요소의 이벤트 핸들러가 동작합니다. 가장 최상단의 요소를 만날 때 까지 이러한 과정이 반복되면서 각각의 요소에 할당된 핸들러가 동작합니다. 이러한 동작이 마치 거품이 일어나는 것과 비슷하다 해서 '버블링' 이라는 명칭이 붙게 되었습니다. 예시를 한 번 볼까요?
<div onclick="alert('div에 할당된 이벤트 핸들러!!!')">
<span>span 영역을 클릭했는데도 DIV 영역에 할당된 핸들러가 동작합니다.</span>
</div>
위의 예시를 동작해보면, 핸들러는 <div>
에 할당되어 있지만, <span>
같은 자식 요소, 혹은 중첩 태그를 클릭해도 동일하게 동작하는 것을 확인할 수 있습니다. 이렇게 동작하는 원인이 바로 버블링입니다.
더 이해하기 쉬운 예제를 살펴볼까요?
<style>
div {
margin: 10px;
border: 1px solid blue;
}
div > div {
border: 1px solid green;
}
div > div > div {
border: 1px solid red;
}
</style>
<div onclick="alert('outer')">OUTER
<div onclick="alert('inner')">INNER
<div onclick="alert('contents')">CONTENTS</div>
</div>
</div>
가장 안쪽의 CONTENTS
를 클릭하면 순서대로 다음과 같은 동작이 일어납니다.
<div>CONTENTS</div>
에 할당된onclick
핸들러가 동작합니다.- CONTENTS 태그를 감싸고 있는
<div>INNER</div>
에 할당된 핸들러가 동작합니다. - INNER 태그를 감싸고 있는
<div>OUTER</div>
에 할당된 핸들러가 동작합니다. document
객체를 만날 때 까지 각 요소에 할당된onclick
핸들러가 동작합니다.
이렇게 동작하기 때문에 <div>CONTENTS</div>
를 클릭하면 CONTENTS -> INNER -> OUTER 순서로 3개의 alert 창이 뜨는 것입니다. 이런 흐름을 '이벤트 버블링'이라고 부릅니다. 해당 이벤트가 일어난 가장 깊은 요소에서 시작해서 그 요소를 감싸고 있는 부모 요소들을 하나씩 타고 올라오면서 발생하기 때문입니다. 마치 물속에서 거품이 올라오는 것과 비슷하죠?
# 거의 모든 이벤트는 버블링이 됩니다.
다만focus
이벤트와 같이 버블링이 되지 않는 이벤트가 조금 있습니다. 그 외에 대부분의 이벤트는 버블링 동작이 일어납니다.
버블링 중단하기
이벤트 버블링은 target 이벤트에서 시작해서 <html
> 요소를 거쳐 document
객체를 만날 때까지 각 노드에서 모두 발생합니다. 어떤 이벤트는 window
객체까지 올라가기도 합니다. 이 때도 위와 동일하게 모든 이벤트 핸들러가 호출됩니다.
그런데 이벤트 핸들러에게 이벤트 처리 동작이 끝나고 나서 event.stopPropagation()
를 사용해서 버블링이 진행되지 않도록 명령하는 것이 가능합니다.
<div onclick="alert('버블링이 발생하지 않습니다')">
<button onclick="event.stopPropagation()">클릭</button>
</div>
위의 예시에서 <button>
을 클릭해도 body.onclick
은 동작하지 않는 것을 확인할 수 있습니다.
# event.stopImmediatePropagation()
한 요소의 특정 이벤트를 처리하는 이벤트 핸들러가 여러개가 있을 경우, 핸들러 중 하나가 버블링을 멈추더라도 나머지 핸들러는 여전히 동작하는 것을 확인할 수 있습니다.event.stopPropagation()
은 현재 요소 위쪽으로 진행되는 버블링은 막아주지만, 다른 핸들러들이 동작하는 것을 막지는 못합니다. 그래서 버블링도 멈추고 요소에 할당된 다른 이벤트 핸들러의 동작도 막기 위해서는event.stopImmediatePropagation()
을 사용해야 합니다. 이 메서드를 사용하면 요소에 할당된 이벤트 핸들러들이 동작하지 않게 됩니다.
event.target과 event.currentTarget
이벤트가 발생하게 되면 해당 이벤트가 어디서 발생했는지, 어떤 이벤트가 발생했는지 등의 자세한 정보를 얻을 수 있습니다. 이 때 이벤트가 발생한 가장 안쪽의 요소를 타겟(target) 요소라고 하고, event.target
을 사용해서 접근할 수 있습니다.
event.target
과 this
(=event.currentTarget
)는 다음과 같은 차이점이 있습니다.
event.target
은 실제 이벤트가 시작된 '타겟'요소 입니다. 버블링이 진행되어도 변하지 않습니다.this
는 '현재'요소로 현재 실행 중인 핸들러가 할당된 요소를 참조합니다.
예시로 위의 두 가지가 어떤 차이를 가지는지 살펴보도록 하죠.
<style>
div {
margin: 10px;
border: 1px solid blue;
}
div > div {
border: 1px solid green;
}
div > div > div {
border: 1px solid red;
}
</style>
<script>
const handleClick = (event) => {
event.target.style.backgroundColor = 'yellow';
setTimeout(() => {
alert('target: ' + event.target.textContent + ', this:' + this.textContent);
event.target.style.backgroundColor = '';
}, 0);
};
</script>
<div onclick="handleClick">OUTER
<div onclick="handleClick">INNER
<div onclick="handleClick">CONTENTS</div>
</div>
</div>
OUTER
를 클릭했을 때는 event.target
과 this
가 동일한 것을 확인할 수 있습니다.
이제 이벤트를 다룰 수 있게 되었네요!! 👏👏👏
다음 시간에는 세 번째 토이프로젝트인 '계산기 만들기'를 진행해보도록 하겠습니다 :)
다음 포스팅
댓글