In-Depth Look at How Events Propagate in JavaScript

William Le

A lesser-known fact about JavaScript events is that they actually “propagate” to nearby HTML elements. Like throwing a pebble into a pond creates ripples on the surface of the water…

Did you know? Events in JavaScript actually propagate to other HTML elements. Let’s start with a small example where we apply two separate click listeners:

<div class="wood">
  wood
  <div class="chuck">
    chuck
  </div> 
</div>

Applying the event listeners…

document
  .querySelector('.wood')
  .addEventListener(
    'click',
    function () { alert('wood') }
  );

document
  .querySelector('.chuck')
  .addEventListener(
    'click',
    function () { alert('chuck') }
  );

Simple enough! Let’s click on the innermost element (div.chuck)…

Whoa! There’s actually two browser alerts saying chuck and then wood!

Event Propagation

You might have guessed what went wrong. The click was actually handled by both event listeners.

“But we only clicked on one of them!”

This is essentially what the term event propagation refers to. The click event fires the first listener but it gets greedy, and just keeps climbing ⤴️ up to the parent and fires every click handler until there aren’t any left.

This phenomenon doesn’t just occur when you have immediate parent-child HTML elements. It’ll skip HTML elements that don’t even have any event listeners!

💡 Did you know? Event propagation is the default behavior for all major browser vendors (Chrome/Firefox/Safari/Edge)

The Fix

Because event propagation can cause unwanted results, JavaScript provides a method called “stopPropagation.” It’s used like this:

document
  .querySelector('.wood')
  .addEventListener(
    'click',
    function (e) {
      e.stopPropagation();
      alert('wood');
    }
  );

document
  .querySelector('.chuck')
  .addEventListener(
    'click',
    function (e) {
      e.stopPropagation();
      alert('chuck');
    }
  );

This effectively contains the click event to one HTML element, and only one click handler fires:

🎻 Voila! It’s fixed!

Using e.stopPropagation() is usually considered good practice whenever you use addEventListener. It’s rather the exception that you want unfettered event propagation.


So far, we’ve only looked at bubbling events (the propagation goes up ☝️ the HTML tree) because it’s the default way that events propagate in JavaScript.

burblin

Bubbling vs. Capturing

Events can actually propagate starting at the outermost HTML element, instead of the innermost HTML element that was clicked. That’s called event capturing.

Let’s look at these two diagrams to help illustrate how bubbling and capturing events differ:

JavaScript event bubbling

Clicking on div.chuck triggers its own click handler, before it propagates ☝️ upward, hence the term bubbling. With capturing events, it actually goes 👇 downward:

JavaScript event capturing

The click event on div.chuck gets “captured” by the outermost HTML parent element (div.wood) because it has its own click handler. That fires, then it continues down 👇 the HTML tree firing click handlers until it resolves back to div.chuck.

🤔 Interesting…


Luckily, since capturing events is reserved for specific scenarios in JavaScript, you have to explicitly enable it in your event listeners:

document
  .querySelector('.wood')
  .addEventListener(
    'click',
    function (e) { alert('wood') },
    true // 👈 boolean argument 
  );

Try clicking on div.chuck below! You’ll see the “wood” then “chuck” alert (I’ve disabled stopPropagation in this example):

Conclusion

Understanding how events naturally propagate in JavaScript is an important step to feeling comfortable using addEventListener. In general, it’s good practice to use e.stopPropagation() so that you’re able to contain propagation. You can always remove e.stopPropagation() in scenarios where you want your events to either bubble up or get captured down.

To learn more about JavaScript event propagation visit Mozilla Developer Network 🤓

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#