One of the most important aspects of DOM is understanding how events work. Propagation refers to how events travel through the DOM tree. The event needs to pass through every node on the DOM until it reaches the end, or if it is forcibly stopped.
Event Bubbling and Capturing
The standard DOM Events describes 3 phases of event propagation:
Capturing phase – the event goes down to the element.
Target phase – the event reached the target element.
Bubbling phase – the event bubbles up from the element.
Bubbling
As the name suggests When an event happens on an element, it first runs the handlers on it, then on its parent, and then up on other ancestors. Almost all events bubble. Let's take the standard FORM > DIV > P example:
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
When we click on the P tag, it runs onclick methods on :
The <p> tag
The outer <div> tag
The outer <form> tag
So, we will see three alerts : 'p' -> 'div' -> 'form'.
Some events that do not bubble include focus, blur, load, unload etc.
Stop Bubbling
Bubbling goes from the target element till the document object, but we can stop the bubbling at a certain element by adding event.stopPropagation() to the element. It is advised not to stop bubbling without a real need. For eg:
In the case of a nested menu
Closing modals by clicking on the negative space
Capturing
Capturing Phase is when the event moves from the top towards the target. Capturing phase is rarely used.
To catch an event, we need to set the capture parameter to true. For ex:
element.addEventListener(..., {capture: true})
//Shorthand
element.addEventListener(..., true)
//Default(Bubbling)
element.addEventListener(..., false)
Bubbling and capturing lay the foundation for “event delegation” – an extremely powerful event handling pattern.
Conclusion
When an event happens – the most nested element where it happens gets labelled as the “target element” (event.target
).
Then the event moves down from the document root to
event.target
, calling handlers assigned withaddEventListener(..., true)
on the way (true
is a shorthand for{capture: true}
).Then handlers are called on the target element itself.
Then the event bubbles up from
event.target
to the root, calling handlers assigned usingon<event>
, HTML attributes andaddEventListener
without the 3rd argument or with the 3rd argumentfalse/{capture:false}
.
Each handler can access event
object properties:
event.target
– the deepest element that originated the event.event.eventPhase
– the current phase (capturing=1, target=2, bubbling=3).
Any event handler can stop the event by calling event.stopPropagation()
, but that’s not recommended, because we can’t really be sure we won’t need it above, maybe for completely different things.
The capturing phase is used very rarely, usually, we handle events on bubbling.
Visual Representation
To see a visual representation of how event propagation works, you can check https://domevents.dev/