이벤트 버블링(Bubbling)과 캡처링(Capturing) 완벽 이해: 이벤트 흐름 제어 마스터하기
웹 페이지에서 사용자가 버튼을 클릭하거나, 마우스를 올리거나, 키보드를 누르는 등 다양한 '이벤트(Event)'가 발생합니다. JavaScript는 이런 이벤트를 감지하고, 그에 따라 특정 동작을 실행하게 만듭니다. 우리는 지난 포스팅에서 addEventListener()
를 통해 이벤트를 다루는 방법을 간략히 살펴보았죠.
그런데 웹 페이지의 요소들은 마치 러시아 인형처럼 서로를 감싸는 구조를 가지고 있습니다. 예를 들어, 웹 페이지(`document`) 안에 `<body>`가 있고, `<body>` 안에 `<div>`가, 그 안에 `<button>`이 있는 식이죠. 이때 `<button>`을 클릭하면, 이 클릭 이벤트가 `<button>`에서만 발생하는 것이 아니라, 그 이벤트를 감싸고 있는 부모 요소들에게도 전달되는 흥미로운 현상이 일어납니다.
이것이 바로 '이벤트 흐름(Event Flow)'의 핵심인 버블링(Bubbling)과 캡처링(Capturing)입니다. 이 두 개념을 이해하면 복잡한 이벤트 처리 로직을 훨씬 깔끔하고 효율적으로 작성할 수 있게 됩니다. 초보자도 쉽게 이해할 수 있도록 그림과 코드를 통해 자세히 알아보겠습니다!
🌊 이벤트 흐름(Event Flow)의 3단계
DOM 이벤트는 클릭이나 키 입력 같은 특정 사건이 발생했을 때, 마치 물결처럼 웹 페이지의 요소들을 따라 전달됩니다. 이 과정은 크게 세 단계로 나뉩니다.
- 캡처링(Capturing) 단계: 이벤트가 위에서 아래로 (최상위 부모 요소부터 클릭된 요소까지) 내려가는 단계입니다.
- 타겟(Target) 단계: 이벤트가 실제로 발생한 타겟 요소에 도달하는 단계입니다.
- 버블링(Bubbling) 단계: 이벤트가 아래에서 위로 (클릭된 요소부터 최상위 부모 요소까지) 다시 올라가는 단계입니다.
대부분의 이벤트는 캡처링 단계에서 시작하여 타겟 단계에 도달한 후, 버블링 단계로 돌아갑니다. 아래 그림을 상상해 보세요.
(위 그림은 이벤트 흐름의 캡처링, 타겟, 버블링 단계를 시각적으로 보여줍니다.)
우리는 addEventListener()
를 사용할 때 이 이벤트 흐름 중 어떤 단계에서 이벤트를 감지할지 설정할 수 있습니다. addEventListener(event, handler, options)
에서 세 번째 인자 options
(또는 useCapture
)가 바로 그것입니다.
⬆️ 이벤트 버블링(Event Bubbling): 기본 동작 방식
이벤트 버블링은 대부분의 이벤트에서 기본적으로 동작하는 방식입니다. 특정 요소에서 이벤트(예: 클릭)가 발생하면, 그 이벤트는 마치 물속의 거품이 위로 떠오르듯이 자신을 감싸고 있는 부모 요소들에게 순차적으로 전달(버블링)됩니다.
예를 들어, `<div id="outer">` 안에 `<div id="inner">`가 있고, 이 `inner`를 클릭하면, 클릭 이벤트는 다음과 같은 순서로 전달됩니다.
- `<div id="inner">` (이벤트가 실제로 발생한 타겟 요소)
- `<div id="outer">` (inner의 부모)
- `<body>` (outer의 부모)
- `<html>` (body의 부모)
- `document` (HTML 문서 전체)
- `window` (브라우저 창)
각각의 요소에 이벤트 리스너가 등록되어 있다면, 이 이벤트는 모든 리스너를 트리거하게 됩니다.
버블링 예시 코드
아래 HTML 구조와 JavaScript 코드를 실행해 보고, 어떤 순서로 메시지가 뜨는지 확인해 보세요.
<!-- HTML -->
<div id="outer" class="outer-box">
Outer Div
<div id="middle" class="middle-box">
Middle Div
<div id="inner" class="inner-box">
Inner Div (클릭!)
</div>
</div>
</div>
// JavaScript
document.addEventListener('DOMContentLoaded', () => {
const outerDiv = document.getElementById('outer');
const middleDiv = document.getElementById('middle');
const innerDiv = document.getElementById('inner');
// 각 요소에 클릭 이벤트 리스너를 추가합니다.
// 세 번째 인자(options)를 생략하거나 false로 두면 버블링 단계에서 동작합니다.
outerDiv.addEventListener('click', () => {
alert('Outer Div 클릭 이벤트 발생 (버블링)');
});
middleDiv.addEventListener('click', () => {
alert('Middle Div 클릭 이벤트 발생 (버블링)');
});
innerDiv.addEventListener('click', () => {
alert('Inner Div 클릭 이벤트 발생 (버블링)');
});
// Inner Div를 클릭하면, 'Inner -> Middle -> Outer' 순서로 alert이 뜹니다.
});
Inner Div (클릭!)
을 클릭하면, 'Inner Div 클릭 이벤트 발생' 알림이 먼저 뜨고, 이어서 'Middle Div 클릭 이벤트 발생', 마지막으로 'Outer Div 클릭 이벤트 발생' 알림이 뜰 것입니다. 이것이 바로 버블링의 증거입니다!
⬇️ 이벤트 캡처링(Event Capturing): 특수한 동작 방식
이벤트 캡처링은 버블링과 반대 방향으로 이벤트가 전달되는 방식입니다. 이벤트가 발생하면, 최상위 부모 요소(예: window
또는 document
)부터 시작하여 이벤트가 발생한 실제 타겟 요소까지 아래로 내려오면서 전달됩니다.
이벤트 캡처링은 기본적으로는 잘 사용되지 않지만, 특정 상황에서 유용하게 쓰일 수 있습니다. 예를 들어, 특정 영역 내의 모든 클릭 이벤트를 가장 먼저 감지하여 처리하고 싶을 때 사용할 수 있습니다.
캡처링 예시 코드
addEventListener()
의 세 번째 인자에 { capture: true }
(또는 예전 방식인 true
)를 설정하면 캡처링 단계에서 이벤트를 감지합니다.
// JavaScript
document.addEventListener('DOMContentLoaded', () => {
const outerDiv = document.getElementById('outer');
const middleDiv = document.getElementById('middle');
const innerDiv = document.getElementById('inner');
// 각 요소에 클릭 이벤트 리스너를 추가합니다.
// 세 번째 인자를 { capture: true }로 설정하여 캡처링 단계에서 동작하도록 합니다.
outerDiv.addEventListener('click', () => {
alert('Outer Div 클릭 이벤트 발생 (캡처링)');
}, { capture: true }); // 또는 true
middleDiv.addEventListener('click', () => {
alert('Middle Div 클릭 이벤트 발생 (캡처링)');
}, { capture: true }); // 또는 true
innerDiv.addEventListener('click', () => {
alert('Inner Div 클릭 이벤트 발생 (캡처링)');
}, { capture: true }); // 또는 true
// Inner Div를 클릭하면, 'Outer -> Middle -> Inner' 순서로 alert이 뜹니다.
});
Inner Div (클릭!)
을 클릭하면, 이번에는 'Outer Div 클릭 이벤트 발생' 알림이 먼저 뜨고, 이어서 'Middle Div 클릭 이벤트 발생', 마지막으로 'Inner Div 클릭 이벤트 발생' 알림이 뜰 것입니다. 캡처링은 이벤트가 위에서 아래로 내려오는 것을 보여줍니다.
🛑 이벤트 흐름 제어하기: stopPropagation()
과 preventDefault()
이벤트 버블링과 캡처링은 유용하지만, 때로는 이벤트가 원치 않는 곳까지 전달되거나, 브라우저의 기본 동작을 막고 싶을 때가 있습니다. 이때 사용할 수 있는 두 가지 중요한 메서드가 있습니다.
1. event.stopPropagation()
: 이벤트 전파 막기
이 메서드는 현재 이벤트가 더 이상 다음 요소로 전달(전파)되는 것을 막습니다. 버블링이든 캡처링이든 상관없이, 이 메서드가 호출된 시점에서 이벤트의 전달은 멈춥니다.
💡 사용 예시: 중첩된 요소에서 특정 버튼만 클릭 이벤트를 처리하고, 부모 요소의 이벤트는 발동시키고 싶지 않을 때.
// JavaScript
document.addEventListener('DOMContentLoaded', () => {
const outerDiv = document.getElementById('outer');
const middleDiv = document.getElementById('middle');
const innerDiv = document.getElementById('inner');
outerDiv.addEventListener('click', () => {
alert('Outer Div 클릭 이벤트 발생');
});
middleDiv.addEventListener('click', () => {
alert('Middle Div 클릭 이벤트 발생');
});
innerDiv.addEventListener('click', (event) => {
alert('Inner Div 클릭 이벤트 발생');
event.stopPropagation(); // ⚠️ 여기서 이벤트 전파를 멈춥니다!
});
// Inner Div를 클릭하면, 'Inner Div 클릭 이벤트 발생'만 뜨고,
// Middle Div와 Outer Div의 이벤트는 발생하지 않습니다.
});
2. event.preventDefault()
: 브라우저 기본 동작 막기
이 메서드는 특정 이벤트에 대한 브라우저의 기본 동작을 막습니다. 예를 들어, <a>
태그를 클릭하면 페이지 이동이 기본 동작인데, `event.preventDefault()`를 사용하면 페이지 이동을 막을 수 있습니다. 폼(form) 제출 시 페이지 새로고침을 막을 때도 사용합니다.
💡 사용 예시: 링크 클릭 시 페이지 이동을 막고 JavaScript로 특정 동작만 수행하고 싶을 때.
<!-- HTML -->
<a id="myLink" href="https://www.google.com">구글로 이동하기</a>
<form id="myForm">
<input type="text">
<button type="submit">제출</button>
</form>
// JavaScript
document.addEventListener('DOMContentLoaded', () => {
const myLink = document.getElementById('myLink');
const myForm = document.getElementById('myForm');
myLink.addEventListener('click', (event) => {
event.preventDefault(); // ⚠️ 링크의 기본 동작(페이지 이동)을 막습니다.
alert('링크 클릭! 하지만 구글로 이동하지 않습니다.');
});
myForm.addEventListener('submit', (event) => {
event.preventDefault(); // ⚠️ 폼 제출 시 기본 동작(페이지 새로고침)을 막습니다.
alert('폼이 제출되었지만 페이지가 새로고침되지 않습니다.');
// 여기에서 Ajax 등으로 실제 데이터 전송 로직을 구현합니다.
});
});
✔️ 결론
이벤트 버블링과 캡처링은 JavaScript 이벤트가 DOM 트리를 통해 어떻게 전달되는지를 이해하는 데 매우 중요한 개념입니다. 대부분의 경우 기본 동작인 버블링으로 충분하지만, 때로는 캡처링을 활용하여 특정 시점에 이벤트를 감지할 필요도 있습니다.
그리고 event.stopPropagation()
으로 이벤트의 전파를 멈추고, event.preventDefault()
로 브라우저의 기본 동작을 막는 방법을 익히면, 여러분은 웹 페이지의 이벤트를 훨씬 더 세밀하게 제어할 수 있게 됩니다. 이 개념들을 잘 활용하여 사용자에게 더 좋고 예측 가능한 웹 경험을 제공하는 멋진 웹 애플리케이션을 만들어 보세요!