Bubbling means that the event propagates from the target element (i.e. the
button
the user clicked) up through its ancestor tree,
starting from the nearest one. By default, all events bubble.
To better understand event bubbling, consider the following HTML example, which we will be referring to for most of this article:
<html>
<body>
<div id="btn-container">
<button class="btn">Click me</button>
</div>
</body>
</html>
const ancestors = [
window, document, document.documentElement,
document.body, document.getElementById('btn-container')
;
]
// Target phase
document.querySelector('.btn').addEventListener('click', e => {
console.log(`Hello from ${e.target}`);
;
})// Bubble phase
.forEach(a => {
ancestors.addEventListener('click', e => {
aconsole.log(`Hello from ${e.currentTarget}`);
;
}); })
If we add an event listener to each element in the tree, as shown above,
we would see a listener fired by the button
first, then each
one of the others firing from the nearest ancestor all the way up to
window
.
Capturing is the exact opposite of bubbling, meaning that the outer event
handlers are fired before the most specific handler (i.e. the one on the
button
). Note that all capturing event handlers are run
first, then all the bubbling event handlers.
You can use event capturing by applying a third argument to
EventTarget.addEventListener
, setting it to true
. For example:
// Capture phase
.forEach(a => {
ancestors.addEventListener('click', event => {
aconsole.log(`Hello from ${e.currentTarget}`);
, true);
}; })
Given this code, we would see a listener fired for each ancestor of the
button
first and then the listener of the
button
would fire.
Having explained event bubbling and capturing, we can now explain the three phases of event propagation:
window
and moves down to document
, the root
element and through ancestors of the target element.
button
the user clicked).
document
and, finally, window
.
Event delegation refers to the idea of delegating event listening to parent elements instead of adding event listeners directly to the event targets. Using this technique, the parent can catch and handle the bubbling events as necessary.
window.addEventListener('click', e => {
if (e.target.className === 'btn') console.log('Hello there!');
; })
In the above example, we delegate event handling from the
button
to window
and use
event.target
to get the original event’s target.
Using the event delegation pattern is advantageous for two reasons: