Event capture, propagation, and bubbling
Check out WesBos video on the topic: Youtube: JS Event Capture, Propagation, and Bubbling
An element is contained inside their parents elements. It could be directly the body
, or could be deeply nested within a structure of elements. When we clicked an element in a webpage, really we actually also clicked on all those ancestor elements wrapped around it. We don't usually see this effect because in most use, we don't attach an event listener to those wrapping elements. This is bubbling concept. The browser see the click not just in the very element, but it is bubbling up. It ripples all the way to the outter most element. To the top of the document.
<body>
<div class="one">
<div class="two">
<div class="three">
</div>
</div>
</div>
</body>
<script>
const divs = document.querySelectorAll("div");
function logText(e) {
console.log(this.classList.value);
}
divs.forEach(div => div.addEventListener("click", logText));
</script>
How does it actually work in Javascript/modern browser?
- Capturing in
- The browser is first doing what is called capture. When you click on the element, it will ripple down from the outter most element inward all the way to the bottom or inner most element, capturing all the events.
body > div 1 > div 2 > div 3
- At that moment, the events are not fired yet. Just captured.
- Console log: (none)
- Bubbling out
- From the bottom, it will start bubbling out, firing the event from innermost element outward.
div 3 < div 2 < div 1 < body
- This is the default behaviour. This is expressed (commonly omitted) in the third argument of the
addEventListener
function as {capture: false}
. It doesn't need to be written down because already assumed as default.
el.addEventListener('click', fn, {capture: false})
el.addEventListener('click', fn)
- Console log:
Manipulations
- Capture: true
- If we want to reverse the order of event firing, we can make the event actually fired as capturing actually happening, from outward in.
el.addEventListener('click', fn, {capture: true})
- Console log:
- Once: true
- An event can be listened and fired as many times as as being triggered. This default (commonly omitted) is expressed in the third parameter as
{once: false}
. If we only want to listen it once, we can set it to true
el.addEventListener('click', fn, {once: true})
once
is a new property. Other way to make an event only listened once is by removing the event listener: removeEventListener
- Stop Propagation
- Propagation refers to reading the nesting containers involved. When we only want to read just one, use
el.stopPropagation()
. It will stop once a container is read. It can be used in conjunction with capture: true
to make the read container is the widest scope one.
- Examples - check the youtube video
-
<script>
const divs = document.querySelectorAll("div");
function logText(e) {
console.log(this.classList.value);
e.stopPropagation();
}
divs.forEach(div => div.addEventListener("click", logText, {
capture: true,
once: true
}));
</script>