Event Bubble & Capture Phases

One of the less understood, but powerful feature of browser events are their phases. According to the W3C level 2 spec there are three phases[1]: AT_TARGET=2, BUBBLING_PHASE=3, and CAPTURING_PHASE=1. Most browsers also implement a fourth phase[2]: NONE=0.

Getting ready

Just a quick note that everything discussed in this article is for modern browsers (all browsers except IE <9). Prior to IE 9, Internet Explorer used its own event system, instead of conforming to the W3C spec. Additionally, while we may show some JavaScript for attaching listeners and stopping events, it is important to know that JavaScript is a separate system from browser events.

To understand the capture and bubble phase, assume we have two elements element2 inside of element1. We assign a click event to element1 and click on element2. The DOM would look something like:

-----------------------------------
| element1                        |
|   -------------------------     |
|   |element2               |     |
|   -------------------------     |
|                                 |
-----------------------------------

Now the capture phase is when the browser searches the DOM starting at the root node (usually DefaultView), until it reaches the node where the event was triggered. The capture phase would look something like:

----------------|  |---------------
| element1      |  |              | <-- event assigned node
|   ------------|  |---------     |
|   |element2    \/         |     | <-- triggering node
|   -------------------------     |
|                                 |
-----------------------------------

Now the bubble phase is when the browser searches the DOM starting at the node where the event was triggered, until it reaches the root node. The bubble phase would look something like:

---------------- /\ ---------------
| element1      |  |              | <-- event assigned node
|   ------------|  |---------     |
|   |element2   |  |        |     | <-- triggering node
|   -------------------------     |
|                                 |
-----------------------------------

Putting both phases together for the W3C model, you get:

---------------|  |-- /\ ----------
| element1     |  |  |  |         | <-- event assigned node
|   -----------|  |--|  |---------|
|   |element2   \/   |  |         | <-- triggering node
|   ------------------------------|
|                                 |
-----------------------------------

The target phase happens between the capture and bubble phases.

How do it…

When attaching an event, the browser defaults to the bubble phase (if the event supports bubble):

document.getElementById('element1').addEventListener('click', function(event) {
    // handle bubble event
});

To attach an event to the capture phase, set the third, optional argument of addEventListener to true:

document.getElementById('element1').addEventListener('click', function(event) {
    // handle capture event
}, true);

Besides event phases, some events also have default actions, such as follow the href of an anchor tag on click. These default actions may occur before or after the DOM event phase cycle and may be prevented using (even ones that occur before the cycle):

document.getElementById('element1').addEventListener('click', function(event) {
    event.preventDefault();
});

Lastly, to stop the event system from completing the phase cycle, call:

document.getElementById('element1').addEventListener('click', function(event) {
    event.stopPropagation();
});

This will end the current phase and all subsequent phases.

Here is a simple demo that attaches two events (one to bubble and the other the the capture phase), and prints the target, targetElement, and phase, to the console:

See the Pen Fun With Event Phases by Matt Snider (@mattsnider) on CodePen.


How it works…

Before discussing the event cycle, lets summarize the steps that happen when the user or browser triggers an event:

  1. Event interface instance created
  2. Put event onto the queue (developer events skip this step)
  3. Event loop processes the event
  4. DOM path to triggering element set
  5. Default action (if applicable)
  6. Capture phase (can be skipped, CAPTURE_PHASE=1)
  7. Target phase (can be skipped, AT_TARGET=2)
  8. Bubble phase (can be skipped and if applicable, BUBBLING_PHASE=3)
  9. Default action (if applicable)

The event is triggered and its instance is created and added to the event queue. There is a single event loop per DOM that pulls the next events off the queue. The browser then calculates the path from the root element to the triggering element. The event may trigger a default action next or after the event phase cycle, and these actions may be prevented during the event phase cycle using event.preventDefault() (not all events may be prevented, such as unload). The developer can check booleans to see if the event is cancellable using event.cancelable, or if it was cancelled using event.defaultPrevented. Then the browser begins the capture, then target, and bubble phases, triggering any necessary callbacks. The phase cycle my be short-circuited at anytime by calling event.stopPropagation(). Lastly, there are events that don’t bubble (such as blur, focus, or scroll). The developer can check this by looking at the boolean event.bubbles. Events that don’t bubble only execute the first two phases.

Calling either event.stopPropagation() or event.stopImmediatePropagation() will cause the the remainder of the capture, target, and bubble phases to stop, triggering the immediate execution of the default action (if one exists for the event). So, when called during the capture phase, the rest of that phase and the other two phases will be skipped, but when called during the bubble phase, just the remaining elements in the bubble phase are skipped. The difference between the two, is event.stopPropagation() affects only the flow for the current event listener, while event.stopImmediatePropagation() will stop propagating for all other event listeners as well.

The event interface passed into callback functions will have two useful DOM pointers, event.target and event.currentTarget. The event.target is the element that the event was assigned to and event.currentTarget is the element currently pointed to by the event phase. When event.target === event.currentTarget you will be in the phase AT_TARGET=2, otherwise you should be in either CAPTURE_PHASE=1 or BUBBLING_PHASE=3 phases.

If a default action triggers before the event phase cycle, and is prevented during the event phase cycle, the user may see weird behavior. For example, a checkbox will trigger a default action that checks the box before the event phase cycle, and if the developer prevents this behavior during the event phase cycle, then it will become unchecked. There is nothing the developer can do to prevent this, so it is important to know what events have default actions and when those actions execute.

Lastly, the default action of some events is to trigger other event, such as hitting the enter key while in an input element inside a form to submit the form, but that is a discussion for another article.

References

  1. W3C Events Interface Spec
  2. MDN - event.eventPhase
  3. JavaScript Event order