If you have any thoughts on my blog or articles and you want to let me know, you can either post a comment below(public) or tell me via this i_kkkp@163.com

Bubbling and Capturing

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>?

  1. First, the handler runs on <p>.

  2. Then, it runs on <div>.

  3. Then, it runs on <form>.

  4. 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:

  1. Capturing phase — The event travels down the hierarchy of elements (from the window).
  2. Target phase — The event reaches the target element.
  3. 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:

Event-Bubbling

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 with addEventListener(..., true) (where true 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 with on<event>, HTML attributes, and addEventListener without a third parameter or with false/{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.

Browser Event Delegation Vue Components and Web Components Custom Elements

Comments