Đến nội dung chính
sang.id.vn
Quay lại

Khi nào bạn thực sự cần capture: true trong addEventListener

English

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().


Share this post on:

Bài trước
Contenteditable lồng nhau: Dùng Shadow DOM để chuyển editing host
Bài tiếp
Cách mình tối ưu tính năng tự động xếp lịch