Event Bubbling v/s Capturing in JavaScript.

Event Bubbling v/s Capturing in JavaScript.

ยท

8 min read

In any web application, events are a key component, and to design an interactive one, one must have a good understanding of how Events Propagate in JavaScript.

In today's article, we will learn how an event propagates and how we can handle it accordingly as per our requirements.

Event

According to MDN, Events are actions or occurrences that happen in the system you are programming, which the system tells you about so your code can react to them.

You can imagine an event is like a trigger that occurs at a certain point in time after a certain action has happened.

Consider the following example which logs ''clicked' in the console whenever the user clicks on the button.

<button id="button">Click Me</button>
const button = document.getElementById('button');

btn.addEventListener('click', () => {
    console.log("clicked");
});

Based on how the user interacts, there are different types of events that occur. Let's take a look at some of the most common ones,

blog2.1.jpg

Event Propagation

The term Propagation simply refers to an act or a process of spreading something from one part to all the other parts.

Event Propagation means that an event occurring on an element will eventually propagate to all of its ancestors (parent, grandparent, and so on), or descendants (child, grandchild, and so on).

If you did not quite get what this definition actually means, don't worry we will understand this in detail with examples ahead.

Event Propagation mainly occurs in two phases those are bubbling and capturing, lets understand these two in detail.

cb-image.png

Event Bubbling

In the Bubbling phase the event firstly gets fired on the element itself and then propagates to the parent element, then to the grandparent then all the way up on other ancestors until the HTML element.

Simply you can think of it as the event bubbles up from the child to all of its ancestors

Consider the following example having two nested div elements and a single button nested all the way down.

<!-- index.html -->
  <div id="grandparent">
   <div id="parent">
    <button id="child">child</button>
  </div>
 </div>

Let's add an event listener to each of the elements, which will listen for the click event.

// index.js
document.getElementById("grandparent").addEventListener("click", () => {
  console.log("Grand Parent");
});

document.getElementById("parent").addEventListener("click", () => {
  console.log("Parent");
});

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

Add CSS.

/* index.css */
#child {
  padding: 0.5rem 1rem;
}

#parent, #grandparent {
  display: flex;
  align-items: center;
  justify-content: center;
}

#parent {
  height: 150px;
  width: 150px;
  background-color: green;
}

#grandparent {
  height: 300px;
  width: 300px;
  background-color: purple;
}

With reference to the above-mentioned code snippets, consider these four scenarios and try to guess what will be the logged to the console?

  1. If grandparent div is clicked.
  2. if parent div is clicked.
  3. if the child button is clicked.
  4. what if we add event listeners to the body and html as well and then click on the button.

If you have guessed it let's check what will be the output for each scenario.

  • Case 1: If the grandparent div is clicked.

    output: Grand Parent.
    

    It is pretty obvious that an event listener is attached on the grandparent div and as soon as we click on it, "grandparent" gets logged to the console.

  • Case 2: If the parent div is clicked.

ezgif.com-gif-maker (1).gif

With reference to the above gif, If we click on the parent div then firstly its own event gets fired, then its parent's event gets fired, and then it propagates all the way up to other ancestors.

Hence the event bubbles out from the parent to the grandparent and it logs Parent and then Grand Parent in the console.

  • Case 3: If the child button is clicked.

ezgif.com-gif-maker.gif

Again, firstly the button's event gets fired and then the event propagates all the way up to other ancestors.

  • Case 4: what if add event listeners to the body and html and then click on the button.
// index.js
document.getElementById("grandparent").addEventListener("click", () => {
  console.log("Grand Parent");
});

document.getElementById("parent").addEventListener("click", () => {
  console.log("Parent");
});

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

document.getElementsByTagName("body")[0].addEventListener("click", () => {
  console.log("Body");
});

document.getElementsByTagName("html")[0].addEventListener("click", () => {
  console.log("Html");
});

blog bc1.jpg

This is is what I meant by the event propagates all the way up to other ancestors.

Event Capturing aka Trickling.

In the capturing phase the event firstly gets fired on the element which is present at the top of the hierarchy (the topmost parent) and then it propagates down to all of its descendants.

Event Capturing is also known as Event Trickling because the event trickles down from the parent to child to grandchild and so on and so forth.

Optional third argument of addEventListener() method.

addEventListener(type, listener, useCapture);

addEventListener method also takes an optional third argument called useCapture, which is a boolean value and by default is set to false. This boolean value will specify how the event will propagate, in the bubbling phase or capturing phase.

If the useCapture flag is set to true, the event will propagate in capturing phase upon the occurrence of the specified event, whereas if the flag is set to false (by default) the event will propagate in the bubbling phase as we have seen in above examples.

Examples of Event Capturing.

We will now reconsider the above-given example but this time with a small modification, we will set the useCapture argument to true.

<!-- index.html -->
  <div id="grandparent">
   <div id="parent">
    <button id="child">child</button>
  </div>
 </div>

adding an event listener to each of the elements, and setting the useCapture flag to true.

// index.js

document.getElementById("grandparent").addEventListener("click", () => {
  console.log("Grand Parent");
}, true);

document.getElementById("parent").addEventListener("click", () => {
  console.log("Parent");
}, true);

document.getElementById("child").addEventListener("click", () => {
  console.log("Child");
}, true);

Now once again can you try to guess what will be the output for the same four scenarios?

  1. If grandparent div is clicked.
  2. if parent div is clicked.
  3. if the child button is clicked.
  4. what if add event listeners to the body and html as well and then click on the button.

If you have guessed it let's check what will be the output for each scenario.

  • Case 1: If the grandparent div is clicked. ezgif.com-gif-maker (2).gif Again here the output will be same as previous because we have only clicked on the grand parent element.

  • Case 2: If the parent div is clicked. ezgif.com-gif-maker (3).gif With reference to the above gif, now firstly the event on grandparent gets fired and then the parent's event gets fired, basically, the event gets trickled down. The output here is totally reversed from what we got in the bubbling phase.

  • Case 3: If child button is clicked. ezgif.com-gif-maker (4).gif Again the event gets fired on the topmost element of the hierarchy and then trickles down to the descendants.

  • Case 4: what if we add event listeners to the body and html as well and then click on the button.

// index.js

document.getElementById("grandparent").addEventListener("click", () => {
  console.log("Grand Parent");
}, true);

document.getElementById("parent").addEventListener("click", () => {
  console.log("Parent");
}, true);

document.getElementById("child").addEventListener("click", () => {
  console.log("Child");
}, true);

document.getElementsByTagName("body")[0].addEventListener("click", () => {
  console.log("Body");
}, true);

document.getElementsByTagName("html")[0].addEventListener("click", () => {
  console.log("Html");
}, true);

ezgif.com-gif-maker (5).gif Again the event gets fired on the topmost element of the hierarchy and then trickles down to the descendants.

Stop Propagation

As we have seen now, be it bubbling or capturing whenever an event propagates to all the ancestors or descendants, the browser has to do a lot of work, which might be unnecessary many of the times and it can heavily affect the performance or our applications. We will now learn how to stop this unnecessary propagation using event.stopPropagation() method.

Lets reconsider the above Event bubbling case 3 example,

<!-- index.html -->
  <div id="grandparent">
   <div id="parent">
    <button id="child">child</button>
  </div>
 </div>
// index.js
document.getElementById("grandparent").addEventListener("click", () => {
  console.log("Grand Parent");
});

document.getElementById("parent").addEventListener("click", () => {
  console.log("Parent");
});

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

Suppose we do not want the event to propagate and should be limited to the child button. To achieve this we will just add event.stopPropagation() method to the callback passed to the eventListener of child button.

// index.js
document.getElementById("child").addEventListener("click", (event) => {
  event.stopPropagation();
  console.log("Child");
});

Before: ezgif.com-gif-maker (4).gif

After using stopPropagation method: ezgif.com-gif-maker (6).gif

Hence this time, we prevented the event from traveling up and executing the callbacks of each parent thereby considerably improving the behavior.

Conclusion

While building web applications, if you are using nested DOM elements and there are events on any of those elements then you need to take into account the event propagation across all of the ancestors and descendants.

Two main phases of Event Propagation:

  • Bubbling: The event bubbles up from the element to all the way up on other ancestors.
  • Capturing: the event trickles down to the element.

We can stop the Event Propagation using the stopPropagation() method on the event.

That was all about Bubbling, Capturing, and stopPropagation. Do check out the resources listed below and If you've reached this point, Thanks for taking your time and reading this article, hope you've enjoyed reading it and found it helpful. Do not hesitate to share your feedback.

Lets connect ๐Ÿค™๐Ÿป

Resources

ย