Luồng sự kiện DOM: Capturing → Target → Bubbling
Khi sự kiện xảy ra trên element, nó đi qua 3 giai đoạn:
- Capturing Phase (Giai đoạn bắt): Từ
window→ ... →parent→target. - Target Phase (Giai đoạn mục tiêu): Tại chính element kích hoạt sự kiện.
- Bubbling Phase (Giai đoạn nổi bọt): Từ
target→parent→ ... →window.
Mặc định addEventListener lắng nghe ở **Bubbling Phase**. Truyền true (hoặc { capture: true }) để lắng nghe ở **Capturing Phase**.
Ví dụ minh họa
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Event Bubbling vs Capturing</title>
<style>
body { font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; padding: 20px; }
.outer {
display: inline-block; width: 300px; height: 150px;
background: #7fff00; padding: 20px; margin: 10px;
}
.inner {
width: 150px; height: 80px;
background: #8a2be2; color: white;
display: flex; align-items: center; justify-content: center;
margin: 20px auto;
}
.tags { display: inline-block; width: 300px; font-weight: bold; margin: 10px 0; }
.result { font-size: 18px; font-weight: 500; color: blueviolet; font-family: monospace; margin-top: 20px; }
</style>
</head>
<body>
<h1>Event Bubbling vs Event Capturing</h1>
<!-- Bubbling (mặc định) -->
<div class="outer" id="outer1"> OUTER (Bubbling)
<div class="inner" id="inner1">INNER</div>
</div>
<!-- Capturing (capture: true) -->
<div class="outer" id="outer2"> OUTER (Capturing)
<div class="inner" id="inner2">INNER</div>
</div>
<br>
<div class="tags">Bubbling (mặc định)</div>
<div class="tags">Capturing (capture: true)</div>
<div class="result" id="result"></div>
<script>
const outer1 = document.getElementById("outer1");
const inner1 = document.getElementById("inner1");
const outer2 = document.getElementById("outer2");
const inner2 = document.getElementById("inner2");
const result = document.getElementById("result");
// BUBBLING (mặc định capture = false)
outer1.addEventListener("click", () => {
result.innerHTML += "🔵 OUTER (bubbling) clicked<br>";
});
inner1.addEventListener("click", () => {
result.innerHTML = "";
result.innerHTML += "🔴 INNER (bubbling) clicked<br>";
});
// CAPTURING (capture = true)
outer2.addEventListener("click", () => {
result.innerHTML = "";
result.innerHTML += "🟢 OUTER (capturing) clicked<br>";
}, true);
inner2.addEventListener("click", () => {
result.innerHTML += "🟡 INNER (capturing) clicked<br>";
}, true);
</script>
</body>
</html>
Kết quả khi click vào INNER div
// Bubbling (outer1/inner1):
🔴 INNER (bubbling) clicked
🔵 OUTER (bubbling) clicked
// Capturing (outer2/inner2):
🟢 OUTER (capturing) clicked
🟡 INNER (capturing) clicked
Dừng sự kiện lan truyền
element.addEventListener('click', (e) => {
e.stopPropagation(); // Ngăn bubbling/capturing tiếp tục
// e.stopImmediatePropagation(); // Cũng ngăn các listener khác trên CÙNG element
});
Event Delegation (Ứng dụng của Bubbling)
Gán 1 listener cho parent thay vì nhiều listener cho children:
<ul id="list">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<script>
document.getElementById('list').addEventListener('click', (e) => {
if (e.target.tagName === 'LI') {
console.log('Clicked:', e.target.textContent);
}
});
</script>
Tóm tắt
- Bubbling: Con → Cha → ... → Window (mặc định).
- Capturing: Window → ... → Cha → Con (cần
{capture: true}). - Target: Element thực sự kích hoạt event.
stopPropagation()dừng lan truyền.- Event Delegation tận dụng bubbling để gán ít listener hơn.