DEV Community

Cover image for Handling JavaScript events efficiently with bubble and capture
Phillip Shim
Phillip Shim

Posted on • Updated on

Event Bubbling in JavaScript Handling JavaScript events efficiently with bubble and capture

 

Javascript enables our web apps to be interactive. It can recognize events generated by a user such as a mouse click, scrolling on a mouse wheel, pressing down on a key of the keyboard, etc... Handling these types of user actions smoothly is important for a great user experience. Today, we will learn how we can efficiently handle JavaScript events using mouse click events as an example.

 


addEventListener method

JavaScript has a built-in method called addEventListener which you can append onto HTML nodes. It takes in a total number of 3 arguments as follows:

  1. Name of an event.
  2. The callback function to run some code when the specified event is triggered.
  3. Optional: the Boolean value of capture. (Set to false by default).

 

<div id="div1">I am a div1</div>
Enter fullscreen mode Exit fullscreen mode
const div1 = document.getElementById("div1");

div1.addEventListener("click", function() {
  console.log("div1 clicked");
});
Enter fullscreen mode Exit fullscreen mode

As you would expect, clicking on 'I am a div' text will print 'div1 clicked' on the console. Let's wrap the text with a new div in the HTML. Can you guess what the output is now if you click on the text?

<div id="div1">
  <div id="div2">I am a div1</div>
</div>
Enter fullscreen mode Exit fullscreen mode
const div1 = document.getElementById("div1");

div1.addEventListener("click", function() {
  console.log("div1 clicked");
});
Enter fullscreen mode Exit fullscreen mode

The result stays the same and prints "I am a div1" even though we clicked on the div with the id of 'div2'.

 


Event bubbling

By default, events bubble in JavaScript. Event bubbling is when an event will traverse from the most inner nested HTML element and move up the DOM hierarchy until it arrives at the element which listens for the event. This move is also popularly known as Event Propagation or Event Delegation.

In the above example, Clicking on the text 'I am a div1' is equivalent to clicking on #div2. Because we have the event listener on the parent element #div1, the event starts the most inner child #div2 and bubbles up.

Here is an additional example. Let's also attach an event listener to the div2 in JavaScript.

<div id="div1">
  <div id="div2">I am a div</div>
</div>
Enter fullscreen mode Exit fullscreen mode
const div1 = document.getElementById("div1");
const div2 = document.getElementById("div2");

div1.addEventListener("click", function() {
  console.log("div1 clicked");
});
div2.addEventListener("click", function() {
  console.log("div2 clicked");
});
Enter fullscreen mode Exit fullscreen mode

Here is the result of event bubbling.

div2 clicked
div1 clicked
Enter fullscreen mode Exit fullscreen mode

Note we can also add event listeners to root level elements such as html and body, the events will bubble until then. This is the hierarchy:

Target -> Body -> HTML -> Document -> Window

 


Stop propagation

Sometimes, you don't want events to trave in a direction, then you can use stopPropagation() of the event object. The event object is provided as a parameter in the callback function.

...

div2.addEventListener("click", function(event) {
  event.stopPropagation();
  console.log("div2 clicked");
});
Enter fullscreen mode Exit fullscreen mode

Result:

div2 clicked
Enter fullscreen mode Exit fullscreen mode

 


Practical Use of Event bubbling

Let's say you are making a to-do list app with pure JavaScript which users can click on a to-do item to toggle it as completed back and forth. Adding individual event listeners to each to-do item is unreasonable because

  1. The list could be very long. (The process becomes tedious. Yes, you can run a loop to add event listeners but having too many event listeners in an app will consume lots of browser memory and will slow down the app)
  2. New todo items can be added dynamically. (No way to add event listeners to them)

We can solve this problem by attaching an event listener to the parent element that contains the list. Take a close look at what the following code does.

<ul class="list">
    <li class="item">Wash dishes</li>
    <li class="item">Walk your dog</li>
</ul>
Enter fullscreen mode Exit fullscreen mode
.completed {
    text-decoration: line-through;
}
Enter fullscreen mode Exit fullscreen mode
const list = document.querySelector(".list");

list.addEventListener("click", e => {
    e.target.classList.toggle("completed")
})
Enter fullscreen mode Exit fullscreen mode

Clicking on an item will toggle class of completed to that specific element which adds a strike-through to the text. It also generates an event object which has target property. Using e.target refers to the DOM that was clicked, which you can add classList and toggle to toggle a class.

 


target vs currentTarget

This is a common interview question that you might encounter. You just learned target refers to the DOM that triggered the event. CurrentTarget will refer to the DOM that the event listener is listening on. Let's console log e.target and e.currentTarget inside the function.

const list = document.querySelector(".list");

list.addEventListener("click", e => {
    console.log(e.target); // <li class="item completed">Walk your dog</li>
    console.log(e.currentTarget); // <ul class="list"></ul>
    e.target.classList.toggle("completed")
})
Enter fullscreen mode Exit fullscreen mode

If the parent element has an event listener but we stop event propagation in the child, the currentTarget refers to the DOM that stopped the propagation

 


Event capturing

To turn this on, pass true as the 3rd argument to the addEventListener method.

Element.addEventListener("click", function(){}, true);
Enter fullscreen mode Exit fullscreen mode

This type of propagation is rarely used. Instead of working from inner to outer it flips the direction and goes from outer to inner. Here is the hierarchy.

Window -> Document -> HTML -> Body -> Target

So you would use this if you want to first get hold of the parent element that the event is listening to. Let's use one of the previous examples.

<div id="div1">
  <div id="div2">I am a div</div>
</div>
Enter fullscreen mode Exit fullscreen mode
const div1 = document.getElementById("div1");
const div2 = document.getElementById("div2");

div1.addEventListener("click", function() {
  console.log("div1 clicked");
}, true);
div2.addEventListener("click", function() {
  console.log("div2 clicked");
});
Enter fullscreen mode Exit fullscreen mode

Result

div1 clicked
div2 clicked
Enter fullscreen mode Exit fullscreen mode

 


Summary

Listening carefully to user interactions and handling them correctly is the first step to make bug-free apps. Remember that bubbling literally bubbles up from inside to outside and capturing is when the event falls down! Thank you for reading!

 

Top comments (6)

Collapse
 
luisuxuk profile image
luisuxuk

99% of JavaScript articles and tutorials over the Internet fail to be effective because unlike you they never mention the practical application of certain features.

Your "Practical Use of Event bubbling" finally help me anchor this piece of information in my brain, now I have a reason to learn it. Well done and thank you!

Collapse
 
shimphillip profile image
Phillip Shim

Thank you for the compliment! Happy that it helped out!

Collapse
 
codingkim profile image
codingkim

Wow. I've learned a lot from you! Thank you

Collapse
 
shimphillip profile image
Phillip Shim

Glad to hear that my content is helpful!

Collapse
 
etoyin profile image
Toyin Adesina

Love this article. I now have a better understanding of Event Bubbling.

Collapse
 
mahrukhsa2 profile image
mahrukhsa2

Good explanation. It helped me to understand the event delegation.