Lets take an example,
We create a div
and set up an onclick
event that shows an alert with the message "div handler"
. Inside the div
, there is a button labeled "Click Me"
.
When you click the button
, the div
event also triggers, even though the button is inside the div
. Isn't that a bit strange? Why does the event run when we click the button
?
Bubbling
In event bubbling, when an event occurs on an element, it first runs on that element, then on its parent, and continues up through its other ancestors.
For instance,
When you click on a paragraph
,
the event first runs on
<p>
,then on the outer
<div>
,then on the next outer
<form>
,and continues upwards until it reaches the
document
object.
The process is called "bubbling." Events move up from the inner element through the parent elements, similar to how a bubble rises in water.
Almost all events bubble.
Not all event bubble, for example,
focus
event does not bubble. There are other examples too, we’ll meet them. But still it’s an exception, rather than a rule, most events do bubble.
Stop Bubbling
We can stop this bubbling. Any handler can decide to stop the bubbling.
To do that we use, event.stopPropagation
.
For instance, here when you click on button, it will not alert anything
Notice how when we click on the button, it doesn't show anything.
event.stopImmediatePropagation()
When there are multiple event handlers on a single element, even if one stops bubbling, the other handlers will still run.
If we want to stop the bubbling for all other events too, we can use event.stopImmediatePropagation()
. This will prevent the handlers on the current element from executing, and after that, no other handler will run.
Don’t stop bubbling when it is not necessary
Bubbling is convenient. Don’t stop it without a need: obvious and architecturally well thought out.
Sometimes event.stopPropagation()
creates hidden pitfalls that later may become problems.
For instance:
We create a nested menu. Each submenu handles clicks on its elements and calls
stopPropagation
so that the outer menu won’t trigger.Later we decide to catch clicks on the whole window, to track users’ behavior (where people click). Some analytic systems do that. Usually the code uses
document.addEventListener('click'…)
to catch all clicks.Our analytic won’t work over the area where clicks are stopped by
stopPropagation
. Sadly, we’ve got a “dead zone”.
There’s usually no real need to prevent the bubbling. A task that seemingly requires that may be solved by other means. One of them is to use custom events, we’ll cover them later. Also we can write our data into the event
object in one handler and read it in another one, so we can pass to handlers on parents information about the processing below.
Capturing
Capturing is reverse of bubbling. Like in bubbling, when a event executes, it moves from current element to its parents. But in capturing, when an event executes, it first run on first parent (like window, document) and then on current element.
Capturing is one of phase of events. It is rarely used in real code, but sometimes can be useful.
The three phases of event propagation are:
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.
Here’s the picture, taken from the specification, of the capturing (1)
, target (2)
and bubbling (3)
phases for a click event on a <td>
inside a table:
That is: for a click on <td>
the event first goes through the ancestors chain down to the element (capturing phase), then it reaches the target and triggers there (target phase), and it goes up (bubbling phase), calling handlers on its way.
The capturing phase was invisible for us, because handlers added using on<event>
-property or using HTML attributes or using two-argument addEventListener(event, handler)
don’t know anything about capturing, they only run on the 2nd and 3rd phases.
To catch an event on the capturing phase, we need to set the handler capture option to true:
element.addEventListener(event, handler, {capture: true})
// or, just "true"
element.addEventListener(event, handler, true)
There are two possible values of the capture
option:
If it’s
false
(default), then the handler is set on the bubbling phase.If it’s
true
, then the handler is set on the capturing phase.
Note that while formally there are 3 phases, the 2nd phase (“target phase”: the event reached the element) is not handled separately: handlers on both capturing and bubbling phases trigger at that phase.
Let’s see both capturing and bubbling in action:
The code sets click handlers on every element in the document to see which ones are working.
If you click on <p>
, then the sequence is:
HTML
→BODY
→FORM
→DIV -> P
(capturing phase, the first listener):P
→DIV
→FORM
→BODY
→HTML
(bubbling phase, the second listener).
Please note, the P
shows up twice, because we’ve set two listeners: capturing and bubbling. The target triggers at the end of the first and at the beginning of the second phase.
There’s a property event.eventPhase
that tells us the number of the phase on which the event was caught. But it’s rarely used, because we usually know it in the handler.
The event.stopPropagation()
during the capturing also prevents the bubbling
The event.stopPropagation()
method and its sibling event.stopImmediatePropagation()
can also be called on the capturing phase. Then not only the futher capturing is stopped, but the bubbling as well.
In other words, normally the event goes first down (“capturing”) and then up (“bubbling”). But if event.stopPropagation()
is called during the capturing phase, then the event travel stops, no bubbling will occur.
Conclusion
Understanding how events move through the DOM is crucial for effective web development. Event bubbling and capturing are two fundamental concepts that dictate how events propagate through the DOM hierarchy. Bubbling allows events to move from the target element up through its ancestors, while capturing does the reverse, starting from the topmost ancestor down to the target. Developers can control this propagation using methods like event.stopPropagation()
and event.stopImmediatePropagation()
, which can prevent events from bubbling or capturing further. However, it's important to use these methods judiciously to avoid unintended consequences, such as creating "dead zones" where events are not captured. By mastering these concepts, developers can create more efficient and responsive web applications, ensuring that event handling is both precise and effective.