J

JavaScript Handbook

Clean • Professional

DOM Event Propagation: Bubbling & Capturing in JavaScript

2 minute

DOM Event Propagation

In the HTML DOM, when an event occurs on an element, it doesn’t just happen in isolation. The event can propagate through the DOM tree, either from the target element up to its ancestors (bubbling) or from the root down to the target (capturing). Understanding this concept is essential for precise event handling.

Event Bubbling

Bubbling is the default behavior in most browsers.

  • The event starts at the target element and bubbles up through all its ancestor elements.
  • Each ancestor can listen for the same event and respond to it.

Example:

<div id="parent">
  <button id="child">Click Me</button>
</div>

<script>
const parent = document.getElementById("parent");
const child = document.getElementById("child");

child.addEventListener("click", () => {
  console.log("Child clicked!");
});

parent.addEventListener("click", () => {
  console.log("Parent clicked!"); // Fires after child
});
</script>

Event Capturing (Trickling)

Capturing is the opposite of bubbling.

  • The event starts at the root of the DOM and travels down to the target element.
  • Less commonly used but useful for intercepting events before they reach the target.

Example:

parent.addEventListener("click", () => {
  console.log("Parent clicked!");
}, true); // true → capturing phase

child.addEventListener("click", () => {
  console.log("Child clicked!");
});

Controlling Event Propagation

Stop Bubbling

child.addEventListener("click", (event) => {
  console.log("Child clicked!");
  event.stopPropagation(); // Prevents event from reaching parent
});

Stop Capturing

  • Using stopPropagation() works for both phases.
  • Prevents the event from propagating further up or down the DOM tree.

Key Points

  • Bubbling: target → parent → root (default)
  • Capturing: root → parent → target (requires capture: true)
  • Use stopPropagation() to prevent unintended event triggering.
  • addEventListener() allows choosing phase via the third parameter:
element.addEventListener("click", handler, { capture: true });

Real-World Example

<div id="outer" style="padding:20px; background:lightblue;">
  Outer
  <div id="inner" style="padding:20px; background:lightgreen;">
    Inner
    <button id="btn">Click</button>
  </div>
</div>

<script>
const outer = document.getElementById("outer");
const inner = document.getElementById("inner");
const btn = document.getElementById("btn");

// Bubbling
outer.addEventListener("click", () => console.log("Outer clicked!"));
inner.addEventListener("click", () => console.log("Inner clicked!"));
btn.addEventListener("click", () => console.log("Button clicked!"));

// Capturing example
outer.addEventListener("click", () => console.log("Outer capturing!"), true);
inner.addEventListener("click", () => console.log("Inner capturing!"), true);
</script>

 

Article 0 of 0