Event bubbling and event capturing are the two interesting concepts of JavaScript. Before diving deep into these fascinating concepts, let us first know about what an event listener is? An event listener is basically a function that waits for an event to occur. That event can be anything like a mouse click event, submitting a form, pressing keys of a keyboard, etc.
An event listener contains three parameters and it can be defined using the following syntax.
<element>.addEventListener(<eventName>, <callbackFunction>, {capture : boolean});
- <element>: The element to which an event listener is attached.
- <eventName>: It can be ‘click’,’key up’,’key down’ etc. events.
- <callbackFunction>: This function fires after the event happened.
- {capture: boolean}: It tells whether the event will be in the capture phase or in the bubbling phase (optional)
Example 1: Let’s take an example to understand event bubbling and event capturing.
HTML
<!DOCTYPE html> < html > < head > < script src = </ script > < style > div { color: white; display: flex; justify-content: center; align-items: center; flex-direction: column; } h2 { color: black; } #grandparent { background-color: green; width: 300px; height: 300px; } #parent { background-color: blue; width: 200px; height: 200px; } #child { background-color: red; width: 100px; height: 100px; } </ style > </ head > < body > < div > < h2 >Welcome To GFG</ h2 > < div id = "grandparent" >GrandParent < div id = "parent" >Parent < div id = "child" >Child</ div > </ div > </ div > </ div > < script > const grandParent = document.getElementById("grandparent"); const parent = document.getElementById("parent"); const child = document.getElementById("child"); grandParent.addEventListener("click", (e) => { console.log("GrandParent"); }, { capture: false }); parent.addEventListener("click", (e) => { console.log("Parent"); }, { capture: false }); child.addEventListener("click", (e) => { console.log("Child"); }, { capture: false }); </ script > </ body > </ html > |
Output:
When we clicked on the div with the child as its id, we should get the output as ‘child’ on our console. But unexpectedly, we are receiving a different output even we have not clicked on divs with parent and grandparent as their id. The concept of event bubbling comes into the picture. The child div lies inside the parent div as well as in the grandparent div. So, when the child div clicked, we indirectly clicked on both parent div and grandparent div. Thus, propagation is moving from inside to outside in the DOM or we can say events are getting bubble up.
Therefore, the process of propagating from the closest element to the farthest away element in the DOM (Document Object Modal) is called event bubbling.
Example 2: In the above example, let us change the value of the third parameter of addEventListener() and see what changes will be made in the output.
HTML
<!DOCTYPE html> < html > < head > < style > div { color: white; display: flex; justify-content: center; align-items: center; flex-direction: column; } h2 { color: black; } #grandparent { background-color: green; width: 300px; height: 300px; } #parent { background-color: blue; width: 200px; height: 200px; } #child { background-color: red; width: 100px; height: 100px; } </ style > </ head > < body > < div > < h2 >Welcome To GFG</ h2 > < div id = "grandparent" >GrandParent < div id = "parent" >Parent < div id = "child" > Child</ div > </ div > </ div > </ div > < script > const grandParent = document.getElementById("grandparent"); const parent = document.getElementById("parent"); const child = document.getElementById("child"); // Changing value of capture parameter as 'true' grandParent.addEventListener("click", (e) => { console.log("GrandParent"); }, { capture: true }); parent.addEventListener("click", (e) => { console.log("Parent"); }, { capture: true }); child.addEventListener("click", (e) => { console.log("Child"); }, { capture: true }); </ script > </ body > </ html > |
It’s clearly visible that the ancestor divs of the child div were printing first and then the child div itself. So, the process of propagating from the farthest element to the closest element in the DOM is called event capturing. Both terms are just opposite of each other.
Example 3: Let’s play around more with the code for better understanding.
HTML
<!DOCTYPE html> < html > < head > < style > div { color: white; display: flex; justify-content: center; align-items: center; flex-direction: column; } h2 { color: black; } #grandparent { background-color: green; width: 300px; height: 300px; } #parent { background-color: blue; width: 200px; height: 200px; } #child { background-color: red; width: 100px; height: 100px; } </ style > </ head > < body > < div > < h2 >Welcome To GFG</ h2 > < div id = "grandparent" >GrandParent < div id = "parent" >Parent < div id = "child" > Child</ div > </ div > </ div > </ div > < script > const grandParent = document.getElementById("grandparent"); const parent = document.getElementById("parent"); const child = document.getElementById("child"); document.addEventListener("click", (e) => { console.log("Document capturing"); }, { capture: true }); grandParent.addEventListener("click", (e) => { console.log("GrandParent capturing"); }, { capture: true }); parent.addEventListener("click", (e) => { console.log("Parent capturing"); }, { capture: true }); child.addEventListener("click", (e) => { console.log("Child capturing"); }, { capture: true }); document.addEventListener("click", (e) => { console.log("Document bubbling"); }, { capture: false }); grandParent.addEventListener("click", (e) => { console.log("GrandParent bubbling"); }, { capture: false }); parent.addEventListener("click", (e) => { console.log("Parent bubbling"); }, { capture: false }); child.addEventListener("click", (e) => { console.log("Child bubbling"); }, { capture: false }); </ script > </ body > </ html > |
Output: If we clicked on the div with id child, then this will be the output.
We can see that the event capturing of event listeners happened first and then the event bubbling happened. This means the propagation of event listeners first goes from outside to inside and then from inside to outside in the DOM.
How to stop event bubbling and event capturing?
In the above example, we can see a parameter “e” (or sometimes called as “event”) in the callback function of addEventListener(). It is an event object which automatically defines when we add an event listener to an element. This object ‘e’ has a function called stopPropagation() which helps to prevent this annoying behavior.
Example 4: Let’s see what will happen when we will click on child div in the below code.
HTML
<!DOCTYPE html> < html > < head > < style > div { color: white; display: flex; justify-content: center; align-items: center; flex-direction: column; } h2 { color: black; } #grandparent { background-color: green; width: 300px; height: 300px; } #parent { background-color: blue; width: 200px; height: 200px; } #child { background-color: red; width: 100px; height: 100px; } </ style > </ head > < body > < div > < h2 >Welcome To GFG</ h2 > < div id = "grandparent" >GrandParent < div id = "parent" >Parent < div id = "child" > Child</ div > </ div > </ div > </ div > < script > const grandParent = document.getElementById("grandparent"); const parent = document.getElementById("parent"); const child = document.getElementById("child"); grandParent.addEventListener("click", (e) => { console.log("GrandParent bubbling"); }); parent.addEventListener("click", (e) => { e.stopPropagation(); //syntax to stop event bubbling console.log("Parent bubbling"); }); child.addEventListener("click", (e) => { console.log("Child bubbling"); }); </ script > </ body > </ html > |
Output:
If we clicked on child div, the propagation is stopped on parent div and does not move to grandparent div. Hence, the event bubbling is prevented.
Note: The event capturing can also be prevented using the same way.
Important points to remember:
- If we do not mention any third parameter in addEventListener(), then by default event bubbling will happen.
- Event bubbling and event capturing happen only when the element and it’s all ancestors have the same event listener (in our case, ‘click’ event) attach to them.
Conclusion: We have learned about event bubbling and event capturing and these are some key points.
- Event capturing means propagation of event is done from ancestor elements to child element in the DOM while event bubbling means propagation is done from child element to ancestor elements in the DOM.
- The event capturing occurs followed by event bubbling.
- If {capture: true} ,event capturing will occur else event bubbling will occur.
- Both can be prevented by using the stopPropagation() method.