Event Bubbling, Event Capturing & Stop Propagation in JavaScript

Event Bubbling, Event Capturing & Stop Propagation in JavaScript

Hello readers, before understanding event bubbling and capturing it is important to understand event propagation. If you are familiar with it skip to the event Bubbling section.

Event Propagation

Event Propagation is a mechanism in which the event propagates or travels through the DOM (Document Object Model) tree of a webpage.

Event propagation occurs in three phases, this is called as Event Propagation Life Cycle:

iEvent propagation life cycle

  1. Capture Phase: The event travels starting from the document object (window), through the HTML element towards the target element.
  2. Target Phase: The event element reaches the target element which generated the event.
  3. Bubble Phase: The event travels from the target element, through its parent towards the window object.

Now, as we have understood how an event flows in the DOM tree. Let's understand Event Bubbling and Capturing.

Event Bubbling and Capturing

Event bubbling and capturing describes the order in which event propagation occurs when a nested child element receives an event trigger and both the child and the parent element have event handlers registered to it.

Setup

let us understand this with an example:

  <body id="ancestor">
    ANCESTOR
    <ul id="parent">
      PARENT
      <li id="child">a</li>
      <li id="child">b</li>
      <li id="child">c</li>
    </ul>
    <script src="src/index.js"></script>
  </body>

This is the HTML code having elements in this order of nesting body > ul > li having corresponding id as ancestor > parent > child

document.querySelector("#ancestor").addEventListener("click", () => {
  console.log("ancestor");
});

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

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

Now, each element is attached to an event listener which just logs its element id upon event trigger. for example, clicking on ancestor will log ancestor in the console.

This is the UI of the program, container a, b and c are the child elements.

image.png

We can check how the event propagates upon clicking on the child element.

default event propagation (3).gif

As clear in the above animation, the event propagates from the child element to the ancestor element. ( child >> parent >> ancestor )

Event Bubbling

When the event propagates from the child or target element to the window object such a mode of event propagation is called Event Bubbling.

Event bubbling is the default mode of event propagation. And thus in the example above for event bubbling, the order of propagation was:

On clicking child element:

  1. first event listener of the child element receives the event.
  2. second, the parent and
  3. third, the ancestor.

Thus, logs in the console for clicking on child element would be:

child
parent
ancestor

On clicking parent element:

  1. first event listener of the parent element receives the event and
  2. second, the ancestor.

Thus, logs in the console for clicking on the parent element would be:

parent
ancestor

Note: event bubble doesn't occur on every element, there are exceptions like focus, blur and scroll events where an event bubble is not observed.

Syntax for Event handling (addEventListener)

Syntax: target.addEventListener(type, listener , useCapture);

  • type: A case-sensitive string representing an event type to listen for. In this example 'click'
  • listener: An object that implements the Event interface when an event of the specified type occurs.
  • useCapture (Optional): A Boolean denoting whether events of this type will be delivered to the registered listener before being delivered to any EventTarget beneath it in the DOM tree.

    If boolean not specified, useCapture defaults to false. i.e. Event Bubbling is enabled.

Event Capturing

When the event propagates from the document object to the target element such a mode of event propagation is called Event Capturing.

For observing event capturing, we need to specify the third argument of the addEventListener as a true. so, the syntax would become: Syntax: target.addEventListener(type, listener , true);

event capturing.gif

On clicking the child element:

  1. first event listener of the ancestor element receives the event.
  2. second, the parent and
  3. third, the child.

Thus, logs in the console for clicking on child element would be:

ancestor 
parent
child

On clicking parent element:

  1. first event listener of the ancestor element receives the event and
  2. second, the parent.

Thus, logs in the console for clicking on the parent element would be:

ancestor
parent

Stop Propagation

The stopPropagation() method of the event interface prevents further propagation of the current event during the event propagation.

for example: If we don't want the event to propagate to the ancestor in our example, we can call the stopPropagation method on the event we get in the callback() function of the addEventListener.

Code snippet for this case:

  document.querySelector("#ancestor").addEventListener("click", () => {
  console.log("ancestor");
});

document.querySelector("#parent").addEventListener("click", (parentEvent) => {
  console.log("parent");
  parentEvent.stopPropagation();
});

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

In the parent element's event listener, we get an event interface, it is named as parentEvent here, upon which we called the stopPropagation method this will prohibit event propagation from the parent element implicating that the event has been completed successfully and no further propagation of event is required.

stop propagation.gif

As you noticed in the above example, when we click on the child element, first the event is received by the child element, and output is logged in the console but as soon as it encounters e.stopPropagation() in the parent event listener, it stops further propagation and does not bubble up in the DOM tree.

Event Handling using Bubbling, Capturing and Stop Event Propagation

let's assume an example case, Consider we want to only trigger the parent element's event listener on click of the child element.

we can achieve this by using useCapture and stopPropagation() method.

Since the default case is the bubbling, on click of the child the first event received would be by a child element, but we don't want that. So let's change the useCapture to true for each event listener to use Event Capturing. Now, on clicking the child element the event received would be by the ancestor element, but we don't want this too.

So how can we get the event to be received by the parent element first? The answer is to make the ancestor element listener to bubble, i.e., useCapture to false

Let's implement this in the code, our code snippet would look like this:

document.querySelector("#ancestor").addEventListener(
  "click",
  () => {
    console.log("ancestor");
  },
  false
);

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

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

Now let's check the output:

get parent event on child click.gif

As seen above, we are making the parent listener receive the event first but we don't want the event to propagate from the parent element. This can be achieved by calling the stopPropagation method on the event received by the parent element's event listener.

So, our code snippet would look as follows:

document.querySelector("#ancestor").addEventListener(
  "click",
  () => {
    console.log("ancestor");
  },
  false
);

document.querySelector("#parent").addEventListener(
  "click",
  (parentEvent) => {
    console.log("parent");
    parentEvent.stopPropagation();
  },
  true
);

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

Final Output:

only parent on child click.gif

Thus, the desired output has been obtained.

This was just a basic example of how we can handle events using the event bubbling, the event capturing and the stop propagation method.

Here's the code sandbox link to try on your own: CodeSandbox Link

TLDR

  • Event Propagation is the flow of events on the event trigger in the DOM tree. It has three phases:- Capture phase, Target phase and Bubble phase.
  • In event bubbling the event bubbles up the DOM tree and in capturing the event trickles down the DOM tree.
  • few events like focus and blur don't bubble up.
  • event propagation could be changed from bubbling to capturing by setting the useCapture to true in the addEventListener.
    • Syntax: target.addEventListener(type, listener , true);
  • event propagation could be controlled by using useCapture and stopPropagation() method on event interface.