Events are everywhere in web programming — input change, mouse move, button click, and page scroll are all forms of events. These are the actions that get generated by the system so that you can respond to them however you like by registering event listeners.
This results in an interactive experience for the user. Understanding how the event model works in modern web browsers can help you build robust UI interactions. Get it wrong, and you have bugs crawling around.
My aim through this article is to elaborate some basics around the event propagation mechanism in the W3C event model. This model is implemented by all modern browsers.
Let’s get started ⏰.
Imagine If we have two HTML elements, element1 and element2, where element2 is the child of element1 as shown in the figure below:
And we add click handlers to both of them like this:
What do you think will be the output when you click element2? 🤔
In event bubbling the innermost target element handles the event first, and then it bubbles up in the DOM tree looking for other ancestor elements with registered event handlers.
💡 In event bubbling the innermost target element handles the event first and then it bubbles up in the DOM tree
Now, the interesting bit is that event flow is not uni-directional, as you might have assumed. The event flow mechanism in the W3C event model is Bi-directional. Surprise Surprise! 😯.
We mostly have been dealing with event bubbling when working with frameworks like React and never think much of another phase which is Event Capturing.
💡 Event bubbling is just one side of the coin; Event capturing is the other.
In the event capturing phase, the event is first captured until it reaches the target element (event.target). And you, as a web developer, can register your event handler in this phase by setting true as the third argument inside the addEventListener method.
By default, it’s false indicating that we are registering this event in the bubbling phase.
Let’s modify our example above to understand this better.
We have added true for useCapture parameter indicating that we are registering our event handler for element1 in the capturing phase. For element2, omitting or passing false will register the event handler in the bubbling phase.
Now, if you click element2, you will see element1 is clicked is printed first followed by element2 is clicked. This is the capturing phase in action.
💡 In the event capturing phase, the event is first captured until it reaches the target element
Here’s the diagram to help you visualise this easily:
The event flow sequence is:
- The click event starts in capturing phase. It looks if any ancestor element of element2 has onClick event handler for the capturing phase.
- The event finds element1, and invokes the handler, printing out element1 is clicked.
- The event flows down to the target element itself(element2) looking for any other elements on its way. But no more event handlers for the capturing phase are found.
- Upon reaching element2, the bubbling phase starts and executes the event handler registered on element2, printing element2 is clicked.
- The event travels upward again looking for any ancestor of the target element(element2) which has an event handler for the bubbling phase. This is not the case, so nothing happens.
So, the key point to remember here is that the whole event flow is the combination of the event capturing phase followed by the event bubbling phase. And as an author of the event handler, you can specify which phase you are registering your event handler in. 🧐
With this new knowledge in our bag, it’s time to look back to our first example and try to analyse why the output was in reverse order. Here’s the first example again so that you’re not creating a scroll event 😛
Omitting the useCapture value registered the event handlers in the bubbling phase for both the elements. When you clicked element2, the event flow sequence was like:
- The “click” event starts in capturing phase. It looks if any ancestor element of element2 has onClick event handler for capturing phase and doesn’t find any.
- The event travels down to the target element itself(element2). Upon reaching element2, the bubbling phase starts and executes the event handler registered on element2, printing element2 is clicked.
- The event travels upwards again looking for any ancestor of the target element(element2) which has an event handler for the bubbling phase.
- This event finds one on element1. The handler is executed and element1 is clicked` is printed out.
Another interesting thing you can do is logging out the eventPhase property of the event. This helps you visualise which phase of the event is currently being evaluated.
Here’s the codepen demo if you like to play with it. Or you can paste the code snippet below in your browser and see it yourself.
Stopping the event propagation
If you wish to prevent further propagation of the current event in any phase, you could invoke stopPropagation method available on the Event object.
So, it means invoking the event.stopPropagation() inside the element1 event handler (in capturing phase), would stop the propagation. And even if you click the element now, it won’t invoke its handler.
The following example demonstrates that:
Note that event.stopPropagation stops the propagation only. It does not, however, prevent any default behaviour from occurring. For example, clicking on links are still processed. To stop those behaviours, you can use event.preventDefault() method.
Finally, here’s another cool JSbin demo if you like to play along and see how can you stop the event propagation via event.stopPropagation.
I hope this article was helpful and has given you some insights. Thanks for reading 😍