What is JavaScript Event Bubbling?
JavaScript Event Bubbling is a method of event propagation through the elements of the Document Object Model (DOM). When an event, such as a click or mouseover, occurs on a specific element, that event is not only triggered on that element but also on all its parent elements. This is called event bubbling because the event ‘bubbles up’ from the most deeply nested element (the event target) through its ancestors all the way up to the root of the DOM tree. This means if you have event listeners on both a child and a parent element, and you trigger the event on the child, both the child’s and the parent’s event listeners will be activated.
Event Bubbling Code Example
Understanding event bubbling is essential for effective event handling and delegation in JavaScript. Let’s look at the following example. We have three nested div elements. Each element has its own click event listener that logs a message to the console. If you click on the innermost div, you’ll see all three messages because of event bubbling.
<!DOCTYPE html>
<html>
<body>
<div id="outer" style="width: 200px; height: 200px; background-color: red;">
Outer Div
<div id="middle" style="width: 150px; height: 150px; background-color: green;">
Middle Div
<div id="inner" style="width: 100px; height: 100px; background-color: blue;">
Inner Div
</div>
</div>
</div>
<script>
document.getElementById("outer").addEventListener("click", function() {
console.log("Outer div clicked");
});
document.getElementById("middle").addEventListener("click", function() {
console.log("Middle div clicked");
});
document.getElementById("inner").addEventListener("click", function() {
console.log("Inner div clicked");
});
</script>
</body>
</html>
In this code, if you click on the “Inner Div”, you’ll see “Inner div clicked”, “Middle div clicked”, and “Outer div clicked” logged to the console, in that same order. This is because the click event bubbles up from the inner div to the outer div.
Remember, the order of the messages reflects the order of event bubbling, from the most deeply nested element outwards.
How to Stop JavaScript Event Bubbling
Sometimes, you’ll want to prevent an event from bubbling up the DOM tree. JavaScript provides two methods for this: stopPropagation()
and stopImmediatePropagation()
.
stopPropagation
This method, when called within an event handler, prevents any further propagation of the event up through the DOM tree, effectively stopping the event from reaching any parent elements of the target element.
In the following example, we have a child div nested inside a parent div. Both divs have click event listeners. When you click on the child div, it will stop the event from bubbling up to the parent.
<!DOCTYPE html>
<html>
<body>
<div id="parentDiv" style="width:200px;height:200px;background-color:red;">
Parent Div
<div id="childDiv" style="width:100px;height:100px;background-color:blue;">
Child Div
</div>
</div>
<script>
document.getElementById("parentDiv").addEventListener("click", function() {
console.log("Parent div clicked");
});
document.getElementById("childDiv").addEventListener("click", function(event) {
event.stopPropagation();
console.log("Child div clicked");
});
</script>
</body>
</html>
In this code, if you click on the “Child Div”, only “Child div clicked” will be logged to the console. The “Parent div clicked” message won’t appear because the stopPropagation()
method has prevented the event from bubbling up to the parent.
stopImmediatePropagation
Normally, if you have multiple event listeners for the same event on the same element, all of them will be executed in order when the event occurs. But this method not only stops the event from bubbling up the DOM tree, but also prevents any other event listeners on the same element from being executed.
<!DOCTYPE html>
<html>
<body>
<div id="myDiv" style="width:200px;height:200px;background-color:red;">
Click Me
</div>
<script>
document.getElementById("myDiv").addEventListener("click", function(event) {
event.stopImmediatePropagation();
console.log("First listener");
});
document.getElementById("myDiv").addEventListener("click", function() {
console.log("Second listener");
});
</script>
</body>
</html>
In this code, we have two click event listeners on the same div. The first listener calls event.stopImmediatePropagation()
. When you click on the div, only “First listener” will be logged to the console. The “Second listener” message won’t appear because stopImmediatePropagation()
has prevented the second event listener from being executed.
If you’re curious about what kind of events you can set on the same element, you can check my post including a complete list of all event listeners in JavaScript.
Event Capturing: The Opposite of Event Bubbling
JavaScript event capturing and event bubbling are two phases of the event propagation model. Event Capturing is similar to event bubbling but from parent to child. It is the first phase of propagation during which the event starts from the topmost parent element and moves down to the target element. This is in contrast to event bubbling, during which the event starts from the target element out to the topmost parent element.
To demonstrate, let’s consider the following example:
<!DOCTYPE html>
<html>
<body>
<div id="parent">
<button id="child">Click me</button>
</div>
<script>
let parent = document.getElementById('parent');
let child = document.getElementById('child');
parent.addEventListener('click', function() {
console.log('Event captured on parent');
}, true); // Setting the useCapture parameter to true
child.addEventListener('click', function() {
console.log('Event triggered on child');
}, true);
</script>
</body>
</html>
In this example, if you click the button (which is the child element), you’ll first see “Event captured on parent” logged to the console before “Event triggered on child”. This is because we’re using event capturing, so the event is handled on the parent element before it reaches the child element.
By default, event handlers are set for the bubbling phase, but they can also be set for the capturing phase using the addEventListener(type, handler, useCapture)
method with useCapture
set as true
like I did in the example above.
Final Notes
Not all events in JavaScript bubble. Whether or not an event bubbles is determined by the event’s bubbles
property which is a read-only property of the Event interface. If set to true, the event bubbles up the DOM tree. If set to false, the event doesn’t bubble up.
Certain events, like focus
, blur
, mouseenter
, mouseleave
, etc., do not bubble. For these events, the bubbles
property returns false
. On the other hand, many commonly used events like click
, keydown
, keyup
, keypress
, mousedown
, etc., do bubble, and for these events, the bubbles
property returns true
.
You can check the ‘bubbles’ page on MDN web docs for more information.