Event bubbling and capturing in JavaScript

In JavaScript, events propagate through event bubbling (from innermost to outermost) and event capturing (opposite direction). Bubbling triggers handlers on child elements first, while capturing starts from the outermost element.

Both allow event handling at different DOM levels. Events are generally processed in three phases in the DOM. They are the capturing, the target, and the bubbling phase.

  • Capturing: The event first travels from the root of the DOM tree down to the target element. During this phase, event listeners on parent elements are triggered before those on child elements.
  • The target: The event reaches the target element and triggers event listeners.
  • Bubbling: The event then propagates back up to the root element, triggering event listeners on ancestor elements.

The addEventListener() method is the recommended way to register an event listener. It gives you finer-grained control of the phase when the listener is activated (capturing vs. bubbling). It works on any event target, not just HTML or SVG elements.

Event bubbling

Event bubbling is a concept in JavaScript where an event that is triggered on a specific element propagates, or “bubbles up,” through the ancestor elements in the DOM tree.

This means that if an event occurs on an element, it first triggers any event handlers on that element, then triggers handlers on its parent, then its grandparent, and so on, all the way up to the root of the DOM tree.

Consider the following HTML:

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

add event listeners to both:

document.getElementById('parent').addEventListener('click', function() {
  console.log('Parent clicked');
});

document.getElementById('child').addEventListener('click', function(event) {
  console.log('Child clicked');
});

When you click the button (#child), the console output will be:

Child clicked
Parent clicked

If you want to prevent an event from propagating up the DOM tree, you can use the event.stopPropagation() method within your event handler:

document.getElementById('child').addEventListener('click', function(event) {
  console.log('Child clicked');
  event.stopPropagation();  // Stop the event from bubbling up
});

Now, when you click the button, the output will be:

Child clicked

The “Parent clicked” message will no longer appear because the event propagation has been stopped.

Event capturing

Event capturing, also known as event trickling, is a phase in the event propagation process in JavaScript where the event starts from the root element and travels down to the target element. This is the opposite of event bubbling.

To set up an event listener for the capture phase, you can pass a third parameter (or options object) to the addEventListener() method and set it to true.

addEventListener(type, listener)
addEventListener(type, listener, options)
addEventListener(type, listener, useCapture)

Consider the following HTML:

<div id="grandparent">
  <div id="parent">
    <button id="child">Click me</button>
  </div>
</div>

and adding event listeners:

document.getElementById('grandparent').addEventListener('click', function() {
  console.log('Grandparent clicked (capturing)');
}, true);  // Capturing phase

document.getElementById('parent').addEventListener('click', function() {
  console.log('Parent clicked (capturing)');
}, true);  // Capturing phase

document.getElementById('child').addEventListener('click', function() {
  console.log('Child clicked (capturing)');
}, true);  // Capturing phase

document.getElementById('child').addEventListener('click', function() {
  console.log('Child clicked (bubbling)');
});  // Bubbling phase

When you click the button (#child), the console output will be:

Grandparent clicked (capturing)
Parent clicked (capturing)
Child clicked (capturing)
Child clicked (bubbling)

Event listeners in the capturing phase are called before event listeners in any non-capturing phases.