Javascript Events – Capturing and Bubbling


Attaching an event handler to a an event is quite straight-forward in JavaScript and trivial in jQuery (soon to become just became even more concise).

View Demo

With all the recent advances in JavaScript (and all facets of web development), I found myself wanting to understand as much as possible about the native DOM and JavaScript API. So I started small and easy, with something I neglected since I first marvelled at a div changing colour when I clicked it: I'm talking about how events of the same type are ordered for an element and its ancestors.

As some of you know, the order of events is controlled by two models: event capturing and event bubbling. In JavaScript we set which "phase" is used with the
addEventListener(type, listener[, useCapture])
function, specifically the third parameter. jQuery seems to only support bubbling. The bind() method has a parameter called preventBubbling which can only be set without a handler argument and seems to be equivalent to attaching a function which stops propagation of the event (we'll get into that later in this post). It is not equivalent to setting addEventListener's useCapture parameter to true.

On to the difference between bubbling and capturing. I give you the following html:

  <div id="parent">
    <div id="child"></div>

If the same event type was attached to #parent and #child there would be two possibilities as to their order, depending on the useCapture parameter:

In the bubbling phase (useCapture = false), the child event handler is executed first followed by the parent. In the capturing phase (useCapture = true), the parent is executed followed by the child. All of this assuming the event was triggered on the child. Simple so far right?

What if our #child grew into a #teenager, got pregnant and had a #baby?

You hook up all your events like so (using click as an example):

   .addEventListener('click', click_handler, true);

  .addEventListener('click', click_handler, false);
  .addEventListener('click', click_handler, false);   

document.addEventListener('click', click_handler, false);

And click on #baby. Since you set #baby's useCapture to true, you might expect the events to fire using the capture phase/bottom up i.e. document - #parent - #teenager - #baby. In actuality, they will fire in bubbling order — from #baby down to the document.

What has happened? It turns out that the browser evaluates the event order in a specific order — recursively! What?! Yes, but settle down, this is not as crazy as it sounds; in fact it a completely sensible way to handle event ordering.

Your click on #baby is first evaluated between the document and its direct child (> #parent) which is on the same "branch" as the event target (#baby). The documents useCapture is set to false (bubble) so it will do #parent's event first. But #parent has its own child elements and order of events. So the next phase begins between #parent and its direct child (#parent > #teenager). #parents useCapture is false too; making us resolve the #teenager > #baby before firing the #parent's handler. This happens for the #teenager too until the phase stops at #baby. This ends the phase at which point the events finally begin firing.

If any of the handlers call stopPropagation() on the event, the phase will end at that handler.

This is obviously one example, that is why I have made a small demo in which you can play around with event capturing and bubbling:

View Demo


No Responses

rss feed

This comment thread is closed. Got something important to say? contact us!