Event delegation and event propagation in JavaScript
1. Event propagation
Event propagation is the way events travel through the DOM tree. There are two directions of propagation:
- Capturing phase (downwards) – the event travels from the
documentelement down to the element that triggered it. - Bubbling phase (upwards) – the event travels from the element that triggered it up through the DOM tree until it reaches
document.
Example of event propagation
Let’s assume we have the following HTML:
<div id="parent">
<button id="child">Click me</button>
</div>
And we add two event listeners:
document.getElementById("parent").addEventListener("click", () => {
console.log("PARENT clicked");
});
document.getElementById("child").addEventListener("click", () => {
console.log("CHILD clicked");
});
If you click on #child, you will see in the console:
CHILD clicked!
PARENT clicked!
This happens because the event bubbles (it travels from the child up to the parent).
Stopping event propagation
If we don’t want an event to bubble up, we can use event.stopPropagation()
document.getElementById("child").addEventListener("click", event => {
event.stopPropagation(); // Stopas propagation
console.log("CHILD clicked");
});
Now, when you click #child, the console will show only: CHILD clicked, and #parent will not receive the event.
Forcing event capturing
By default, event listeners work in the bubbling phase, which means the event for #child will fire first, followed by the event for #parent.
However, you can reverse this order by assigning the listener to the capturing phase, by passing true as the third argument to addEventListener.
document.getElementById("parent").addEventListener("click", () => {
console.log("PARENT clicked (capturing)");
}, true);
Now #parent will fire before #child, because it handles the event during the capturing phase.
2. Event delegation
Event delegation is a technique of assigning an event listener to a parent element instead of attaching one to each child individually. It’s useful when elements are added to the DOM dynamically (e.g., via AJAX).
document.getElementById("parent").addEventListener("click", event => {
if (event.target.classList.contains("child")) {
console.log("Child clicked: ", event.target);
}
});
If we later add a new element:
const newChild = document.createElement("div");
newChild.classList.add("child");
newChild.textContent = "New element";
document.getElementById("parent").appendChild(newChild);
We will still be able to detect clicks on the new element, even though we didn’t attach a separate addEventListener to it.