Problem Statement
What is event bubbling in JavaScript?
Explanation
Event bubbling is when an event triggered on a child element propagates up through its parent elements to the root.
The event starts at the target element where it occurred. It then bubbles up through each ancestor element. Each ancestor's event handlers are triggered in order.
Event capturing is the opposite, going from root down to target. Most events bubble, but some like focus and blur do not.
You can stop bubbling with event.stopPropagation. This prevents the event from reaching parent elements.
Event delegation uses bubbling to handle events on multiple children with one parent listener.
Understanding bubbling is essential for event delegation and preventing unintended behavior.
Code Solution
SolutionRead Only
// HTML structure:
// <div id="outer">
// <div id="middle">
// <button id="inner">Click</button>
// </div>
// </div>
const outer = document.getElementById('outer');
const middle = document.getElementById('middle');
const inner = document.getElementById('inner');
// Add listeners to all three
outer.addEventListener('click', () => {
console.log('Outer clicked');
});
middle.addEventListener('click', () => {
console.log('Middle clicked');
});
inner.addEventListener('click', () => {
console.log('Inner clicked');
});
// When button clicked, output:
// 'Inner clicked'
// 'Middle clicked'
// 'Outer clicked'
// Event bubbles from inner to outer!
// STOPPING PROPAGATION
inner.addEventListener('click', (event) => {
console.log('Inner clicked');
event.stopPropagation(); // Stop bubbling!
});
// Now only 'Inner clicked' appears
// EVENT PHASES
// 1. Capture phase (root to target)
outer.addEventListener('click', () => {
console.log('Outer capture');
}, true); // true = capture phase
// 2. Target phase
inner.addEventListener('click', () => {
console.log('Target');
});
// 3. Bubble phase (target to root)
outer.addEventListener('click', () => {
console.log('Outer bubble');
}, false); // false = bubble phase (default)
// Output when inner clicked:
// 'Outer capture' (capture phase)
// 'Target' (target phase)
// 'Outer bubble' (bubble phase)
// EVENT DELEGATION
// Instead of adding listener to each item
const list = document.getElementById('list');
// Add one listener to parent
list.addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
console.log('Clicked:', event.target.textContent);
}
});
// Works for dynamically added items too!
const newItem = document.createElement('li');
newItem.textContent = 'New Item';
list.appendChild(newItem);
// Clicking new item works without adding listener!
// PRACTICAL EXAMPLE - Modal
const modal = document.getElementById('modal');
const modalContent = document.querySelector('.modal-content');
const closeBtn = document.querySelector('.close');
// Close modal when clicking outside
modal.addEventListener('click', () => {
modal.style.display = 'none';
});
// Don't close when clicking content
modalContent.addEventListener('click', (event) => {
event.stopPropagation(); // Don't bubble to modal
});
// Close button
closeBtn.addEventListener('click', () => {
modal.style.display = 'none';
});
// PREVENTING DEFAULT AND BUBBLING
const link = document.querySelector('a');
link.addEventListener('click', (event) => {
event.preventDefault(); // Don't follow link
event.stopPropagation(); // Don't bubble
console.log('Link clicked but not followed');
});
// event.stopImmediatePropagation()
// Stops other handlers on same element too
inner.addEventListener('click', (event) => {
console.log('First handler');
event.stopImmediatePropagation();
});
inner.addEventListener('click', () => {
console.log('Second handler'); // Won't run
});