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

How Forms Submit When Pressing Enter

Lately, I have been trying to perfect the x-browser behavior of pressing the enter key inside of an input field while using one of the in-page popups on Mint.com. The desired behavior is to capture the form submission and execute the same JavaScript action as is triggered by the call-to-action button. So, first I did some research trying to find out how the HTML specifications dictate that forms should behave. Unfortunately, I did not ...

Using an EventProvider

For the Client-Side Storage problem that I have been working on with YUI, I was introduced to the EventProvider Interface, which provides a better way of handling the CustomEvents attached to an object. In Yahoo!s own words:

EventProvider is designed to be used with YAHOO.augment to wrap CustomEvents in an interface that allows events to be subscribed to and fired by name. This makes it possible for implementing code to subscribe to an event ...

Upgraded to Wordpress 2.7

Part of my 2008 New Years Resolution was to improve the layout of this blog. I have made a few minor stylistic changes this year, but the vast majority of changes will be coming in the next couple of weeks. It is amazing how much I postpone when I dont think of myself as a client. Anyway, the first major change is upgrading the software to the newest Wordpress (v2.7). Im sorry for any inconvenience ...

Event Dispatcher

Modern web applications behave a lot like desktop applications, with nearly asynchronous updates, pre-caching of events and DOM, animations, drag and drop, etc. However, all these features come with a price, and a web application developer constantly considers add a new feature versus degrading site performance. Todays topic illustrates how you can reduce the cost of events leveraging an event dispatcher, EventDispatcher. From Widipedia:

Multiple dispatch or multimethods is the feature of some object-oriented programming ...

Simulating Events Using YUI

Sometimes when managing pages with unobtrusive JavaScript, especially ones leveraging events that need to bubble, two code paths are required: one for the event handler that properly bubbles, and another for the same action triggered by code. For example, a click event is attached to a div, containing many children, including several input elements. If the client clicks on one of the inputs, then both the input and the divclick events fire. However, if ...

Listening for Browser Changes

Have you ever wanted to adjust your design when the user resizes their browser or changes their font-size? While some browsers have added special (non-standard) events, so that you can listener for these changes, most browser have not. Today I will show you how to use YUI custom events to subscribe to manage events that fire when the user changes the browsers font-size or resizes their browser window. Here is the module BrowserEventMonitor.js:

Example ...

Event Bubbling and Event Capture

Today’s article is going to be a quick insight into what is meant when developers talk about event bubbling and capturing. Although there is a lot to write about this topic, Peter-Paul Koch explains most of it at Quirksmode: Event Ordering. He does not mention Safari in his writeup, but it also supports both bubbling and capture.

Article updated on Nov. 26, 2011

This article is ancient. It was revisited in Event ...

Catching All JavaScript Errors in All Browsers

I recently wrote an article about the JavaScript onerror event. The onerror event is really powerful in that you can prevent all JavaScript errors from ever being shown to the user, as well as capturing each error seen by users and sending it to a server for logging. The major downside is that only the major browsers (IE and FireFox) support window.onerror, so we don’t get the same goodness in Safari, Opera, and other ...

Window onError Event

Window.onerror is an obscure and little used event with surprising usefulness. Before the days of FireBug it was difficult to debug in the browser, where an error occurred and why. Now that we have FireBug, most JavaScript works wonderfully in FireFox and decently in other browsers. And even with the robust debugging, there will still be user configurations that were not tested against, who see JavaScript errors that you can never capture. The onerror event ...