The event model in JavaScript is based on a propagation system that allows events to pass from parents to children and vice versa through the document structure.
It is important to understand the model to control the order in which DOM elements respond to an event.
For example, when we click a button in a form, we are clicking both on the form and the button.
Which element do you want to react to the click first? And how do we indicate this to JavaScript? This is where the event propagation model comes into play.
This model has three phases:
Phase | Execution Order |
---|---|
Capturing | From document to target |
Target | On the target itself |
Bubbling | From the target to document |
The target is the lowest element in the DOM that receives the event.
That is, events occur in the document and pass down to the button. Then, they return back up in the opposite direction to the document.
Let’s see each of them 👇
Event capturing
This is the first phase, in which the event propagates from the root document to the target element.
In our example,
- The event would move from the root document to the button
- It would trigger the event handlers of all the intermediate elements in between.
It is less frequently used to capture events than bubbling.
Event bubbling
In this phase, the event propagates from the target element to the document.
This phase occurs after the capturing phase and allows parent elements to handle events generated in their child elements.
That is, in the example,
- The event would move from the button to the document
- Similarly, it would trigger the event handlers of all the intermediate elements in between.
This is the default phase in most browsers and is commonly used in event management.
Not all events support bubbling, but most do (for example, click
does, but focus
does not).
Capturing in one phase or another
DOM events always go through these phases. And we can choose in which phase we want to respond to the event.
To do this, we have a parameter in addEventHandler
.
true
: Capturing.false
(or unspecified): Bubbling.
Example of Event Capturing
To capture an event during the propagation phase, we need to specify the capture: true
option when adding an event handler.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Event Capturing</title>
</head>
<body>
<div id="container">
<button id="button">Click here</button>
</div>
<script>
document.getElementById('button').addEventListener('click', function() {
console.log('Button clicked');
}, true); // true indicates that the capturing phase should be used
document.getElementById('container').addEventListener('click', function() {
console.log('Container clicked');
}, true);
document.body.addEventListener('click', function() {
console.log('Body clicked');
}, true);
</script>
</body>
</html>
In this case,
- We have set
true
in the third parameter ofaddEventHandler
(capturing phase) - The message “Body clicked” is printed first, followed by “Container clicked” and finally “Button clicked”
- That is, we see how we capture the event as it propagates from the root document to the button.
Example of Event Bubbling
To capture an event during the bubbling phase, we need to specify the capture: false
(or not use it, since that is the default value) when adding an event handler.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Event Bubbling</title>
</head>
<body>
<div id="container">
<button id="button">Click here</button>
</div>
<script>
document.getElementById('button').addEventListener('click', function() {
console.log('Button clicked');
});
document.getElementById('container').addEventListener('click', function() {
console.log('Container clicked');
});
document.body.addEventListener('click', function() {
console.log('Body clicked');
});
</script>
</body>
</html>
In this example,
- We did not put anything in the third parameter of
addEventHandler
(bubbling phase) - When clicking the button, the message “Button clicked” is printed first, followed by “Container clicked” and “Body clicked”
- That is, we see how we capture the event as it propagates from the button upwards.
Stopping event propagation
Sometimes, it is necessary to prevent an event from propagating through the DOM, either during the capturing or bubbling phase.
To do this, JavaScript provides two useful methods:
Method | Description |
---|---|
stopPropagation() | Stops the propagation of the event, preventing it from continuing to travel to parent elements. |
stopImmediatePropagation() | In addition to stopping propagation, it prevents other handlers of the same event from executing on the same element. |
Example of stopPropagation
Use stopPropagation()
if you want to prevent the event from propagating to other elements in the DOM.
<div>
<button id="btn">Click here</button>
</div>
document.getElementById("btn").addEventListener("click", function(event) {
event.stopPropagation();
console.log("Button clicked");
});
document.querySelector("div").addEventListener("click", function() {
console.log("Div clicked");
});
- When clicking the button, only “Button clicked” will be shown in the console.
- The event will not reach the
<div>
element thanks tostopPropagation()
.
Example of stopImmediatePropagation
Use stopImmediatePropagation()
if you need to ensure that other handlers on the same element do not execute.
<button id="button">Click here</button>
document.getElementById("button").addEventListener("click", function(event) {
console.log("First handler executed");
event.stopImmediatePropagation();
});
document.getElementById("button").addEventListener("click", function() {
console.log("This handler will not execute");
});
- When clicking the button, only “First handler executed” will be shown.
- The second handler will not execute due to the use of
stopImmediatePropagation()
.