Phần lớn tutorial giải thích event capturing vs bubbling trên lý thuyết nhưng chẳng bao giờ chỉ ra khi nào thực sự cần dùng. Đây là một trường hợp thực tế mà capture: true là đáp án đúng.
Sự kiện DOM lan truyền qua ba giai đoạn:
CAPTURE (từ trên xuống)
document → div.container → button
TARGET
button
BUBBLE (từ dưới lên)
document ← div.container ← button
Mặc định, addEventListener('click', fn) đăng ký ở giai đoạn bubble. Để đăng ký ở giai đoạn capture:
element.addEventListener("click", fn, { capture: true });
Vấn đề
Dropdown kiểu “click ra ngoài để đóng”. Một button bên trong gọi stopPropagation(), ngăn sự kiện click bubble lên document, nên handler đóng dropdown không bao giờ chạy.
<div id="dropdown-container">
<button id="toggle">Toggle Dropdown</button>
<div id="dropdown" class="hidden">
<p>Dropdown content</p>
<button id="inner-action">Action</button>
</div>
</div>
const toggle = document.getElementById("toggle");
const dropdown = document.getElementById("dropdown");
const container = document.getElementById("dropdown-container");
toggle.addEventListener("click", () => {
dropdown.classList.toggle("hidden");
});
// Đóng dropdown khi click ra ngoài
document.addEventListener("click", e => {
if (!container.contains(e.target)) {
dropdown.classList.add("hidden");
}
});
// Dòng này ngăn click lan tới document
document.getElementById("inner-action").addEventListener("click", e => {
e.stopPropagation();
});
Cách sửa
Dùng capture: true cho listener trên document. Giai đoạn capture chạy từ trên xuống trước khi bất kỳ stopPropagation() nào ở giai đoạn bubble có thể can thiệp:
document.addEventListener(
"click",
e => {
if (!container.contains(e.target)) {
dropdown.classList.add("hidden");
}
},
true // giai đoạn capture
);
Lưu ý
Khi gỡ listener giai đoạn capture, phải truyền cùng flag:
document.addEventListener("click", handler, true);
document.removeEventListener("click", handler, true);
Gọi stopPropagation() trong giai đoạn capture sẽ ngăn sự kiện tới được các phần tử con.
Các use case khác: ủy quyền sự kiện focus/blur (các sự kiện này không bubble), analytics/logging cần bắt mọi click bất kể stopPropagation().