13: Event bubbling and capture

Understanding how events are propagated downwards (capture) and then back upwards (bubbling) through the Document Object Model.

Learning Goals

At the end of this Lesson, you will be able to:

  • Understanding how events are propagated downwards (capture) and then back upwards (bubbling) through the DOM.

In your javascript/exercises folder, create a new sub-folder named event-propagation.

Save the exercise file below to this new javascript/exercises/event-propagation sub-folder.

Event Propagation: Exercises

Event capture and bubbling

In JavaScript, event capture and bubbling refer to two different ways that events are propagated ('travel') through the DOM (Document Object Model) from the event target to its parent (above) and ancestor (below)elements.

Event capture

Event capture occurs first and propagates from the top of the DOM tree (the window and document objects) down to the target element, such as the button that has been clicked.

Once the event reaches the target element, the event capture phase is now complete. The event does not travel further down the DOM tree.

By default, the event on the target element is not triggered during this capture phase.

Event bubbling

Bubbling, on the other hand, occurs when the event has reached the target element. By default, the event is then triggered.

After that happens, the event travels from the target element back up to the top of the DOM tree.

During the bubbling phase, the event might meet a parent that has also an event listener attached. If that happens, that event is also triggered and its associated function is also called.

Here's an example to illustrate event capture and bubbling:


<h2>Some heading here</h2>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Laudantium explicabo similique optio neque, ipsum ducimus minima cumque eos molestiae laboriosam debitis. Amet ea cum praesentium quia pariatur doloremque atque quod!</p>
<div id="outerDIV">
    <button id="bigBtn">Change background to Green</button>
</div>

As you can see, there is an outer <div> element and an inner <button> element nested inside it.

In the code below, a click event listener is attached to each element. A third parameter of the addEventListener() method is used to specify whether to use event capture or bubbling.

When you click on the inner button element, the event first travels down the DOM tree from the window object to the target element, triggering the outer event listener function ("Outer clicked") along the way because we've used event capture. Then, the event reaches the inner button element and triggers its own event listener function ("Inner clicked"). Finally, the event travels back up the DOM tree from the target element to the window object, triggering the outer event listener function ("Outer clicked") again, but this time because we've used event bubbling.

const outerDIV = document.getElementById('outerDIV')
const bigBtn = document.getElementById("bigBtn");

// Event listener executed during the capturing (first, down) phase.
outerDIV.addEventListener('click', () => {
    console.log(`Capture phase: click event on outer div - but action executed`);
    document.body.style.backgroundColor= 'red';
});

// Event listener executed during the bubble (second, up) phase.
outerDIV.addEventListener('click', () => {
    console.log(`Capture phase: click event on outer div - but action executed`);
    document.body.style.backgroundColor= 'red';
});

// Event listener executed during the bubbling (second, up) phase.
bigBtn.addEventListener('click', (e) => {
    console.log('Capture phase: click event on button - but action executed');
    console.log('Bubble phase:  action now executed');
    // e.stopPropagation();
    document.body.style.backgroundColor= 'green';
},false);

Preventing event propagation

To prevent event propagation (either event capture or event bubbling) on a button in JavaScript, you can use the event.stopPropagation() method in the event listener function that is attached to the button.

Here's an example of how to use event.stopPropagation() to prevent event bubbling when a button is clicked:

const button = document.querySelector('button');

button.addEventListener('click', function(event) {
    event.stopPropagation(); // prevent event from bubbling up
    console.log('Button clicked');
});
			
document.addEventListener('click', function() {
    console.log('Document clicked');
});

In this example, we've attached a click event listener to a button element and used event.stopPropagation() to prevent the click event from bubbling up the DOM tree and triggering the click event listener attached to the document object.

Without event.stopPropagation(), when the button is clicked, both the button event listener function ("Button clicked") and the document event listener function ("Document clicked") would be triggered. But now only the button event listener function is triggered and the document event listener function is prevented from being triggered.