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:
- Capture Phase: The event travels starting from the document object (window), through the HTML element towards the target element.
- Target Phase: The event element reaches the target element which generated the event.
- 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.
We can check how the event propagates upon clicking on the child element.
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:
- first event listener of the child element receives the event.
- second, the parent and
- third, the ancestor.
Thus, logs in the console for clicking on child element would be:
child
parent
ancestor
On clicking parent element:
- first event listener of the parent element receives the event and
- 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);
On clicking the child element:
- first event listener of the ancestor element receives the event.
- second, the parent and
- third, the child.
Thus, logs in the console for clicking on child element would be:
ancestor
parent
child
On clicking parent element:
- first event listener of the ancestor element receives the event and
- 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.
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:
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:
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
totrue
in the addEventListener.- Syntax:
target.addEventListener(type, listener , true);
- Syntax:
- event propagation could be controlled by using
useCapture
andstopPropagation()
method on event interface.