DOM Event Bubbling and Capturing

Download DOM for free


Events fired on DOM elements don't just affect the element they're targeting. Any of the target's ancestors in the DOM may also have a chance to react to the event. Consider the following document:

<!DOCTYPE html>
<meta charset="utf-8" />
  <p id="paragraph">
    <span id="text">Hello World</span>

If we just add listeners to each element without any options, then trigger a click on the span...

document.body.addEventListener('click', function(event) {
  console.log("Body clicked!");
window.paragraph.addEventListener('click', function(event) {
  console.log("Paragraph clicked!");
window.text.addEventListener('click', function(event) {
  console.log("Text clicked!");

...then the event will bubble up through each ancestor, triggering each click handler on the way:

Text clicked!
Paragraph clicked!
Body clicked!

If you want one of your handlers to stop the event from triggering any more handlers, it can call the event.stopPropagation() method. For example, if we replace our second event handler with this:

window.paragraph.addEventListener('click', function(event) {
  console.log("Paragraph clicked, and that's it!");

We would see the following output, with body's click handler never triggered:

Text clicked!
Paragraph clicked, and that's it!

Finally, we have the option to add event listeners that trigger during "capture" instead of bubbling. Before an event bubbles up from an element through its ancestors, it's first "captured" down to the element through its ancestors. A capturing listener is added by specifying true or {capture: true} as the optional third argument to addEventListener. If we add the following listeners to our first example above:

document.body.addEventListener('click', function(event) {
  console.log("Body click captured!");
}, true);
window.paragraph.addEventListener('click', function(event) {
  console.log("Paragraph click captured!");
}, true);
window.text.addEventListener('click', function(event) {
  console.log("Text click captured!");
}, true);

We'll get the following output:

Body click captured!
Paragraph click captured!
Text click captured!
Text clicked!
Paragraph clicked!
Body clicked!

By default events are listened to in the bubbling phase. To change this you can specify which phase the event gets listened to by specifying the third parameter in the addEventListener function. (To learn about capturing and bubbling, check remarks)

element.addEventListener(eventName, eventHandler, useCapture)

useCapture: true means listen to event when its going down the DOM tree. false means listen to the event while its going up the DOM tree.

window.addEventListener("click", function(){alert('1: on bubble')}, false);
window.addEventListener("click", function(){alert('2: on capture')}, true);

The alert boxes will pop up in this order:

  • 2: on capture
  • 1: on bubble

Real-world use cases

Capture Event will be dispatch before Bubble Event, hence you can ensure than an event is listened to first if you listen to it in its capture phase.

if you are listening to a click event on a parent element, and another on its child, you can listen to the child first or the parent first, depending on how you change the useCapture parameter.

in bubbling, child event gets called first, in capture, parent first

in bubbling, child event gets called first, in capture, parent first


<div id="parent">
   <div id="child"></div>


child.addEventListener('click', function(e) {
   alert('child clicked!');

parent.addEventListener('click', function(e) {
   alert('parent clicked!');
}, true);

Setting true to the parent eventListener will trigger the parent listener first.

Combined with e.stopPropagation() you can prevent the event from triggering the child event listener / or the parent. (more about that in the next example)

Event Delegation