Preface
Let’s start with an example.
A handler is assigned to the <div>
, but if you click on any nested tags (e.g., <em>
or <code>
), the handler will also run:
<div onclick="alert('The handler!')">
<em>If you click on <code>EM</code>, the handler on <code>DIV</code> runs.</em>
</div>
So, if you click on EM, the handler on DIV runs.
Bubbling
The bubbling principle is straightforward.
When an event occurs on an element, it first runs the handler on that element, then runs the handler on its parent element, and continues up to handlers on other ancestors.
Suppose we have 3 layers of nesting FORM > DIV > P
, each with its own handler:
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
Now, what happens if you click on <p>
?
First, the handler runs on
<p>
.Then, it runs on
<div>
.Then, it runs on
<form>
.Finally, it runs on the document.
This behavior is called “event bubbling” because it bubbles up from the element like a bubble.
So, if we click on <p>
, we will see 3 alerts: p → div → form.
event.target
The handler on a parent element can always access detailed information about where the event actually occurred.
The element that triggered the event, the deepest nested one, is called the target element, and it can be accessed through event.target
.
Note the difference with this
(which equals event.currentTarget
):
this
is the “current” element at the time the handler is running, and it remains the same.event.target
is the “target” element when the event happens, and it can be any element; it changes during the bubbling process.
Let’s illustrate this with an example!
For instance, if we have a handler form.onclick
, it can “capture” all clicks inside the form. Regardless of where the click happens, it will bubble up to <form>
and run the handler.
Note: Here, all events are bound to the form instead of individual elements.
In the form.onclick
handler:
this
(=event.currentTarget
) is the<form>
element because the handler runs on it.event.target
is the actual clicked element inside the form.
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
A click shows both <code>event.target</code> and <code>this</code> to compare:
<form style="background-color: green;
position: relative;
width: 150px;
height: 150px;
text-align: center;
cursor: pointer;">FORM
<div style="background-color: blue;
position: absolute;
top: 25px;
left: 25px;
width: 100px;
height: 100px;">DIV
<p style="background-color: red;
position: absolute;
top: 25px;
left: 25px;
width: 50px;
height: 50px;
line-height: 50px;
margin: 0;">P</p>
</div>
</form>
<script>
form=document.querySelector('form');
form.onclick = function(event) {
// Output the event target and the current element
// (this=the current element=form, event.target=the clicked element)
alert("target = " + event.target.tagName + ", this=" + this.tagName);
};
</script>
</body>
</html>
## Stopping Bubbling
Bubbling events ascend from the target element. Usually, they rise all the way up to `<html>`, then to the document object, and some events even reach the window, calling all handlers on their way.
However, any handler can decide that the event has been fully processed and stop the bubbling.
The method used to stop bubbling is `event.stopPropagation()`.
For example, if you click `<button>`, the `body.onclick` here won't work:
```html
<body onclick="alert(`the bubbling doesn't reach here`)">
<button onclick="event.stopPropagation()">Click me</button>
</body>
Capturing
Another phase of event handling is called “capturing.”
The DOM event standard describes the three phases of event propagation:
- Capturing phase — The event travels down the hierarchy of elements (from the window).
- Target phase — The event reaches the target element.
- Bubbling phase — The event starts to bubble up from the element.
Below is an illustration of clicking on <td>
in a table, taken from the specification:
In other words: Clicking on <td>
, the event first descends through the ancestor chain to the element (capturing phase), then reaches the target (target phase), and finally ascends (bubbling phase), calling handlers along the way.
So far, we have only discussed bubbling because the capturing phase is rarely used. Typically, we don’t see it.
Handlers added with on<event>
attributes or using HTML attributes or with addEventListener(event, handler)
with two arguments have no knowledge of capturing; they run only in the second and third phases.
Summary
When an event occurs — the element nested deepest where the event happened is marked as the “target element” (event.target
).
- Then, the event moves down from the document root to the
event.target
, invoking handlers assigned withaddEventListener(..., true)
(wheretrue
is a shorthand for{capture: true}
). - Next, the handler on the target element itself is called.
- Finally, the event bubbles up from
event.target
to the root, calling handlers assigned withon<event>
, HTML attributes, andaddEventListener
without a third parameter or withfalse
/{capture: false}
.
Each handler has access to properties of the event
object:
event.target
— The element deepest in the hierarchy that triggered the event.event.currentTarget
(=this
) — The current element handling the event (the one with the handler).event.eventPhase
— The current phase (capturing=1, target=2, bubbling=3).
Any event handler can stop the event by calling event.stopPropagation()
, but it’s not recommended because we might not be sure if we genuinely don’t need the event to bubble up for other purposes.
The capturing phase is rarely used, and usually, events are handled during the bubbling phase. There’s a reason for this logic.
Event handlers work the same way. Code that sets a handler on a specific element gains the most detailed information about that element. A handler specific to a <td>
might be perfectly tailored for that <td>
, knowing all about it. So, it should get the chance first. Then, its immediate parent also knows context-specific details but fewer, and so on, until handling general concepts and running the last handler on the topmost element.
Comments