β What Is an Event?
An event is an action that happens in the browser:
A user clicks a button
A key is pressed
A page loads
The user scrolls
π JavaScript can detect these actions and respond to them.
<div class="infographic-whatisevent">
<style>
.infographic-whatisevent {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #1a8a5f;
--good-soft: #e1f2ea;
box-sizing: border-box;
max-width: 880px;
margin: 0 auto;
font-family: 'Inter', system-ui, sans-serif;
color: var(--ink);
background: var(--paper);
overflow-x: hidden;
}
.infographic-whatisevent[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4fbf8f;
--good-soft: #16281f;
}
.infographic-whatisevent * { box-sizing: border-box; }
.infographic-whatisevent h1 { font-family: 'Fraunces', Georgia, serif; font-weight: 600; margin: 0; }
.infographic-whatisevent p { margin: 0; }
.infographic-whatisevent button { font-family: inherit; }
.we-wrap { padding: clamp(1.25rem, 5vw, 2.5rem) clamp(1.1rem, 5vw, 3rem) 2rem; }
.we-hero { text-align: center; margin-bottom: 1.5rem; }
.we-hero h1 { font-size: clamp(1.35rem, 4.5vw, 1.85rem); }
.we-hero p { color: var(--ink-soft); margin-top: 0.45rem; font-size: 0.85rem; }
/* pipeline */
.we-pipeline { display: flex; align-items: center; justify-content: center; margin-bottom: 1.6rem; }
.we-pnode { display: flex; flex-direction: column; align-items: center; gap: 0.4rem; width: 110px; }
.we-pdot { width: 48px; height: 48px; border-radius: 50%; border: 1.5px solid var(--line); background: var(--paper-alt); display: flex; align-items: center; justify-content: center; transition: border-color 0.3s ease, background 0.3s ease, transform 0.3s ease; }
.we-pdot svg { width: 22px; height: 22px; stroke: var(--ink-soft); transition: stroke 0.3s ease; }
.we-plabel { font-size: 0.66rem; text-align: center; color: var(--ink-soft); font-weight: 600; }
.we-pnode.is-active .we-pdot { border-color: var(--accent); background: var(--accent-soft); transform: scale(1.12); }
.we-pnode.is-active .we-pdot svg { stroke: var(--accent); }
.we-pnode.is-active .we-plabel { color: var(--accent); }
.we-pnode.is-done .we-pdot { border-color: var(--good); background: var(--good-soft); transform: scale(1); }
.we-pnode.is-done .we-pdot svg { stroke: var(--good); }
.we-pconnect { width: 34px; height: 2px; background: var(--line); margin: 0 -0.2rem 1.5rem; position: relative; }
.we-pconnect-fill { position: absolute; inset: 0; width: 0%; background: var(--accent); transition: width 0.3s ease; }
/* trigger zone */
.we-triggers { display: grid; grid-template-columns: 1fr 1fr; gap: 0.7rem; }
@media (max-width: 480px) { .we-triggers { grid-template-columns: 1fr; } }
.we-trigger { border: 1.5px solid var(--line); background: var(--paper-alt); border-radius: 8px; padding: 0.7rem; text-align: center; cursor: pointer; transition: border-color 0.25s ease, background 0.25s ease; font-family: inherit; }
.we-trigger:hover { border-color: var(--accent); }
.we-trigger svg { width: 20px; height: 20px; stroke: var(--accent); margin-bottom: 0.35rem; }
.we-trigger-label { font-size: 0.76rem; font-weight: 600; color: var(--ink); display: block; }
.we-trigger-sub { font-size: 0.64rem; color: var(--ink-soft); display: block; margin-top: 0.1rem; }
.we-trigger.is-scroll { overflow-y: auto; height: 76px; cursor: default; }
.we-trigger.is-scroll .we-scroll-inner { height: 220px; display: flex; align-items: center; justify-content: center; flex-direction: column; }
.we-trigger-group { display: flex; flex-direction: column; }
.we-scroll-hint { text-align: center; font-size: 0.66rem; font-weight: 600; color: var(--accent); margin-bottom: 0.3rem; }
.we-console { margin: 1.2rem auto 0; max-width: 360px; text-align: left; border: 1.5px solid var(--line); background: var(--paper-alt); padding: 0.55rem 0.8rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.72rem; min-height: 2.2em; }
.we-console .we-cline { color: var(--good); opacity: 0; transition: opacity 0.3s ease; }
.we-console .we-cline.is-shown { opacity: 1; }
.we-console .we-placeholder { color: var(--ink-soft); opacity: 1; }
.we-console .we-placeholder.is-hidden { opacity: 0; position: absolute; }
.we-note { text-align: center; font-size: 0.8rem; color: var(--ink-soft); margin-top: 1.4rem; }
.we-note strong { color: var(--ink); }
.we-reset { display: block; margin: 0.8rem auto 0; border: 1px solid var(--line); background: var(--paper-alt); color: var(--ink-soft); padding: 0.4rem 0.9rem; font-size: 0.72rem; font-weight: 600; cursor: pointer; }
.we-reset:hover { border-color: var(--accent); color: var(--accent); }
@media (prefers-reduced-motion: reduce) {
.infographic-whatisevent *, .infographic-whatisevent *::before, .infographic-whatisevent *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="we-wrap">
<div class="we-hero">
<p>Try any of these below β each one is a real event firing.</p>
</div>
<div class="we-pipeline">
<div class="we-pnode" id="peEvent">
<div class="we-pdot"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/></svg></div>
<div class="we-plabel">event happens</div>
</div>
<div class="we-pconnect" id="peConn1"><div class="we-pconnect-fill"></div></div>
<div class="we-pnode" id="peListen">
<div class="we-pdot"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 8.5a6.5 6.5 0 1 1 13 0c0 6-6 6-6 10a3.5 3.5 0 1 1-7 0"/><path d="M15 8.5a2.5 2.5 0 0 0-5 0v1a2 2 0 1 1 0 4"/></svg></div>
<div class="we-plabel">JS listens</div>
</div>
<div class="we-pconnect" id="peConn2"><div class="we-pconnect-fill"></div></div>
<div class="we-pnode" id="peRun">
<div class="we-pdot"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m7 11 2-2-2-2"/><path d="M11 13h4"/><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/></svg></div>
<div class="we-plabel">code runs</div>
</div>
</div>
<div class="we-triggers">
<button class="we-trigger" id="triggerClick" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.5 21h5"/><path d="M12 3v3"/><path d="M18.5 8.5 16 11"/><path d="M2 12h3"/><path d="M19 12h3"/><path d="m4 4 2.5 2.5"/><path d="M9 17a4 4 0 0 1 6 0"/></svg>
<span class="we-trigger-label">Click me</span>
<span class="we-trigger-sub">click event</span>
</button>
<div class="we-trigger" id="triggerKey" tabindex="0">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 8h.01"/><path d="M12 12h.01"/><path d="M14 8h.01"/><path d="M16 12h.01"/><path d="M18 8h.01"/><path d="M6 8h.01"/><path d="M7 16h10"/><path d="M8 12h.01"/><rect width="20" height="16" x="2" y="4" rx="2"/></svg>
<span class="we-trigger-label">Click here, then press any key</span>
<span class="we-trigger-sub">keydown event</span>
</div>
<button class="we-trigger" id="triggerLoad" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 16h5v5"/></svg>
<span class="we-trigger-label">Simulate page load</span>
<span class="we-trigger-sub">load event</span>
</button>
<div class="we-trigger-group">
<span class="we-scroll-hint">Scroll down β</span>
<div class="we-trigger is-scroll" id="triggerScroll">
<div class="we-scroll-inner">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="20" x="5" y="2" rx="7"/><path d="M12 6v4"/></svg>
<span class="we-trigger-label">Scroll inside this box</span>
<span class="we-trigger-sub">scroll event</span>
</div>
</div>
</div>
</div>
<div class="we-console">
<span class="we-placeholder" id="wePlaceholder">// waiting for an eventβ¦</span>
<span class="we-cline" id="weOut"></span>
</div>
<button class="we-reset" id="weReset" type="button">reset</button>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-whatisevent')
|| document.querySelector('.infographic-whatisevent');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
const peEvent = wrapper.querySelector('#peEvent');
const peListen = wrapper.querySelector('#peListen');
const peRun = wrapper.querySelector('#peRun');
const conn1 = wrapper.querySelector('#peConn1 .we-pconnect-fill');
const conn2 = wrapper.querySelector('#peConn2 .we-pconnect-fill');
const out = wrapper.querySelector('#weOut');
const placeholder = wrapper.querySelector('#wePlaceholder');
let runToken = 0;
function fireEvent(message) {
const token = ++runToken;
[peEvent, peListen, peRun].forEach(n => n.classList.remove('is-active', 'is-done'));
conn1.style.width = '0%';
conn2.style.width = '0%';
out.classList.remove('is-shown');
placeholder.classList.remove('is-hidden');
setTimeout(() => { if (token !== runToken) return; peEvent.classList.add('is-active'); placeholder.classList.add('is-hidden'); }, 20);
setTimeout(() => { if (token !== runToken) return; peEvent.classList.remove('is-active'); peEvent.classList.add('is-done'); conn1.style.width = '100%'; }, 280);
setTimeout(() => { if (token !== runToken) return; peListen.classList.add('is-active'); }, 400);
setTimeout(() => { if (token !== runToken) return; peListen.classList.remove('is-active'); peListen.classList.add('is-done'); conn2.style.width = '100%'; }, 660);
setTimeout(() => { if (token !== runToken) return; peRun.classList.add('is-active'); out.textContent = message; out.classList.add('is-shown'); }, 780);
setTimeout(() => { if (token !== runToken) return; peRun.classList.remove('is-active'); peRun.classList.add('is-done'); }, 1050);
}
wrapper.querySelector('#triggerClick').addEventListener('click', () => {
fireEvent('"click" fired \u2192 button.addEventListener("click", ...) ran');
});
const keyZone = wrapper.querySelector('#triggerKey');
keyZone.addEventListener('keydown', (e) => {
fireEvent('"keydown" fired \u2192 key pressed: "' + e.key + '"');
});
keyZone.addEventListener('click', () => keyZone.focus());
wrapper.querySelector('#triggerLoad').addEventListener('click', () => {
fireEvent('"load" fired \u2192 window.addEventListener("load", ...) ran');
});
const scrollZone = wrapper.querySelector('#triggerScroll');
let scrollTimer = null;
scrollZone.addEventListener('scroll', () => {
clearTimeout(scrollTimer);
scrollTimer = setTimeout(() => {
fireEvent('"scroll" fired \u2192 window.addEventListener("scroll", ...) ran');
}, 80);
});
wrapper.querySelector('#weReset').addEventListener('click', () => {
runToken++;
[peEvent, peListen, peRun].forEach(n => n.classList.remove('is-active', 'is-done'));
conn1.style.width = '0%';
conn2.style.width = '0%';
out.classList.remove('is-shown');
out.textContent = '';
placeholder.classList.remove('is-hidden');
scrollZone.scrollTop = 0;
});
})();
</script>β Event Flow (Event Models)
When a user interacts with a webpage (click, keypress, submit, etc.), the event doesnβt just fire on one element.
It travels through the DOM tree in a fixed order, which is called Event Flow
Event flow defines how an event propagates (travels) through the DOM hierarchy.
<div class="infographic-eventflow">
<style>
.infographic-eventflow {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #1a8a5f;
--good-soft: #e1f2ea;
--amber: #a4720a;
--amber-soft: #faf1de;
box-sizing: border-box;
max-width: 880px;
margin: 0 auto;
font-family: 'Inter', system-ui, sans-serif;
color: var(--ink);
background: var(--paper);
overflow-x: hidden;
}
.infographic-eventflow[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4fbf8f;
--good-soft: #16281f;
--amber: #d9a94a;
--amber-soft: #2c2413;
}
.infographic-eventflow * { box-sizing: border-box; }
.infographic-eventflow h1 { font-family: 'Fraunces', Georgia, serif; font-weight: 600; margin: 0; }
.infographic-eventflow p { margin: 0; }
.infographic-eventflow button { font-family: inherit; }
.ef-wrap { padding: clamp(1.25rem, 5vw, 2.5rem) clamp(1.1rem, 5vw, 3rem) 2rem; }
.ef-hero { text-align: center; margin-bottom: 1.3rem; }
.ef-hero h1 { font-size: clamp(1.3rem, 4.5vw, 1.8rem); }
.ef-hero p { color: var(--ink-soft); margin-top: 0.45rem; font-size: 0.84rem; }
/* phase tracker */
.ef-phases { display: flex; align-items: center; justify-content: center; margin-bottom: 1.5rem; }
.ef-phase { display: flex; flex-direction: column; align-items: center; gap: 0.3rem; width: 110px; }
.ef-phase-num { width: 30px; height: 30px; border-radius: 50%; border: 1.5px solid var(--line); background: var(--paper-alt); display: flex; align-items: center; justify-content: center; font-family: 'IBM Plex Mono', monospace; font-size: 0.72rem; font-weight: 700; color: var(--ink-soft); transition: border-color 0.3s ease, background 0.3s ease, color 0.3s ease, transform 0.3s ease; }
.ef-phase-label { font-size: 0.62rem; text-align: center; color: var(--ink-soft); font-weight: 600; }
.ef-phase.is-active .ef-phase-num { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); transform: scale(1.15); }
.ef-phase.is-active .ef-phase-label { color: var(--accent); }
.ef-phase.is-target .ef-phase-num { border-color: var(--good); background: var(--good-soft); color: var(--good); }
.ef-phase.is-target .ef-phase-label { color: var(--good); }
.ef-phase.is-bubble .ef-phase-num { border-color: var(--amber); background: var(--amber-soft); color: var(--amber); }
.ef-phase.is-bubble .ef-phase-label { color: var(--amber); }
.ef-phase-connect { width: 30px; height: 2px; background: var(--line); margin-bottom: 1.4rem; }
/* nested DOM */
.ef-nest { border: 1.5px solid var(--line); border-radius: 8px; padding: 1.1rem; background: var(--paper-alt); transition: border-color 0.6s ease, background 0.6s ease; position: relative; }
.ef-nest-label { position: absolute; top: -9px; left: 10px; background: var(--paper); padding: 0 0.4rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.62rem; color: var(--ink-soft); transition: color 0.6s ease; }
.ef-nest.is-lit { border-color: var(--accent); background: var(--accent-soft); }
.ef-nest.is-lit .ef-nest-label { color: var(--accent); font-weight: 700; }
.ef-nest.is-bubble-lit { border-color: var(--amber); background: var(--amber-soft); }
.ef-nest.is-bubble-lit .ef-nest-label { color: var(--amber); font-weight: 700; }
.ef-target-btn {
display: block; margin: 0.5rem auto 0; border: 1.5px solid var(--line); background: var(--paper);
color: var(--ink); padding: 0.55rem 1.1rem; font-size: 0.82rem; font-weight: 600; border-radius: 6px; cursor: pointer;
transition: border-color 0.3s ease, background 0.3s ease, color 0.3s ease, transform 0.3s ease;
}
.ef-target-btn.is-target { border-color: var(--good); background: var(--good-soft); color: var(--good); transform: scale(1.06); }
.ef-console { margin: 1.2rem auto 0; max-width: 380px; text-align: left; border: 1.5px solid var(--line); background: var(--paper-alt); padding: 0.55rem 0.8rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.68rem; min-height: 5.5em; line-height: 1.6; }
.ef-console .ef-cline { display: block; opacity: 0; transition: opacity 0.25s ease; }
.ef-console .ef-cline.is-shown { opacity: 1; }
.ef-console .ef-cline.cap { color: var(--accent); }
.ef-console .ef-cline.tgt { color: var(--good); font-weight: 700; }
.ef-console .ef-cline.bub { color: var(--amber); }
.ef-note { text-align: center; font-size: 0.8rem; color: var(--ink-soft); margin-top: 1.4rem; }
.ef-note strong { color: var(--ink); }
@media (prefers-reduced-motion: reduce) {
.infographic-eventflow *, .infographic-eventflow *::before, .infographic-eventflow *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="ef-wrap">
<div class="ef-hero">
<p>One click on the button β but the event travels through every ancestor, twice.</p>
</div>
<div class="ef-phases">
<div class="ef-phase" id="phCapture">
<div class="ef-phase-num">1</div>
<div class="ef-phase-label">capturing</div>
</div>
<div class="ef-phase-connect"></div>
<div class="ef-phase" id="phTarget">
<div class="ef-phase-num">2</div>
<div class="ef-phase-label">target</div>
</div>
<div class="ef-phase-connect"></div>
<div class="ef-phase" id="phBubble">
<div class="ef-phase-num">3</div>
<div class="ef-phase-label">bubbling</div>
</div>
</div>
<div class="ef-nest" id="nestHtml">
<div class="ef-nest-label"><html></div>
<div class="ef-nest" id="nestBody">
<div class="ef-nest-label"><body></div>
<div class="ef-nest" id="nestDiv">
<div class="ef-nest-label"><div id="outer"></div>
<button class="ef-target-btn" id="targetBtn" type="button"><button id="target"> click me</button>
</div>
</div>
</div>
<div class="ef-console" id="efConsole">
<span class="ef-cline cap" id="l1">capturing: html</span>
<span class="ef-cline cap" id="l2">capturing: body</span>
<span class="ef-cline cap" id="l3">capturing: div#outer</span>
<span class="ef-cline tgt" id="l4">target: button#target</span>
<span class="ef-cline bub" id="l5">bubbling: div#outer</span>
<span class="ef-cline bub" id="l6">bubbling: body</span>
<span class="ef-cline bub" id="l7">bubbling: html</span>
</div>
<p class="ef-note">Down through every ancestor first (<strong>capturing</strong>), hits the <strong>target</strong>, then back out the same way (<strong>bubbling</strong>).</p>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-eventflow')
|| document.querySelector('.infographic-eventflow');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
const nestHtml = wrapper.querySelector('#nestHtml');
const nestBody = wrapper.querySelector('#nestBody');
const nestDiv = wrapper.querySelector('#nestDiv');
const targetBtn = wrapper.querySelector('#targetBtn');
const phCapture = wrapper.querySelector('#phCapture');
const phTarget = wrapper.querySelector('#phTarget');
const phBubble = wrapper.querySelector('#phBubble');
const lines = ['l1', 'l2', 'l3', 'l4', 'l5', 'l6', 'l7'].map(id => wrapper.querySelector('#' + id));
let token = 0;
function reset() {
[nestHtml, nestBody, nestDiv].forEach(n => n.classList.remove('is-lit', 'is-bubble-lit'));
targetBtn.classList.remove('is-target');
[phCapture, phTarget, phBubble].forEach(p => p.classList.remove('is-active', 'is-target', 'is-bubble'));
lines.forEach(l => l.classList.remove('is-shown'));
}
targetBtn.addEventListener('click', () => {
const t = ++token;
reset();
const STEP = 550;
let delay = 250;
phCapture.classList.add('is-active');
const capture = [nestHtml, nestBody, nestDiv];
capture.forEach((el, i) => {
setTimeout(() => { if (t !== token) return; el.classList.add('is-lit'); lines[i].classList.add('is-shown'); }, delay);
delay += STEP;
});
setTimeout(() => {
if (t !== token) return;
phCapture.classList.remove('is-active');
phTarget.classList.add('is-active', 'is-target');
targetBtn.classList.add('is-target');
lines[3].classList.add('is-shown');
}, delay);
delay += STEP + 300;
setTimeout(() => {
if (t !== token) return;
phTarget.classList.remove('is-active');
phBubble.classList.add('is-active', 'is-bubble');
}, delay);
const bubble = [nestDiv, nestBody, nestHtml];
bubble.forEach((el, i) => {
setTimeout(() => {
if (t !== token) return;
el.classList.remove('is-lit');
el.classList.add('is-bubble-lit');
lines[4 + i].classList.add('is-shown');
}, delay);
delay += STEP;
});
setTimeout(() => { if (t !== token) return; phBubble.classList.remove('is-active'); }, delay + 200);
});
})();
</script>π§© Visual DOM Structure Example
<div class="infographic-dompath3">
<style>
.infographic-dompath3 {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #1a8a5f;
--good-soft: #e1f2ea;
box-sizing: border-box;
max-width: 880px;
margin: 0 auto;
font-family: 'Inter', system-ui, sans-serif;
color: var(--ink);
background: var(--paper);
overflow-x: hidden;
}
.infographic-dompath3[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4fbf8f;
--good-soft: #16281f;
}
.infographic-dompath3 * { box-sizing: border-box; }
.infographic-dompath3 h1 { font-family: 'Fraunces', Georgia, serif; font-weight: 600; margin: 0; }
.infographic-dompath3 p { margin: 0; }
.infographic-dompath3 button { font-family: inherit; }
.dp3-wrap { padding: clamp(1.5rem, 6vw, 2.5rem) clamp(1.1rem, 5vw, 2.5rem) 2rem; }
.dp3-hero { text-align: center; margin-bottom: 1.2rem; }
.dp3-hero h1 { font-size: clamp(1.3rem, 4.5vw, 1.75rem); }
.dp3-hero p { color: var(--ink-soft); margin-top: 0.4rem; font-size: 0.84rem; }
.dp3-code { background: #1e1e1e; color: #d4d4d4; border-radius: 5px; padding: 0.6rem 0.8rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.68rem; line-height: 1.6; margin-bottom: 1.5rem; white-space: pre; overflow-x: auto; }
.tok-tag { color: #569cd6; }
.tok-attr { color: #9cdcfe; }
.tok-aval { color: #ce9178; }
.dp3-chain { display: flex; flex-direction: column; }
.dp3-row { display: flex; align-items: center; gap: 0.7rem; }
.dp3-linkcol { width: 46px; display: flex; flex-direction: column; align-items: center; flex-shrink: 0; }
.dp3-node {
width: 46px; height: 46px; border-radius: 50%; border: 1.5px solid var(--line); background: var(--paper-alt);
display: flex; align-items: center; justify-content: center; flex-shrink: 0;
transition: border-color 0.4s ease, background 0.4s ease, transform 0.4s ease;
}
.dp3-node svg { width: 19px; height: 19px; stroke: var(--ink-soft); transition: stroke 0.4s ease; }
.dp3-node.is-lit { border-color: var(--accent); background: var(--accent-soft); }
.dp3-node.is-lit svg { stroke: var(--accent); }
.dp3-node.is-target { border-color: var(--good); background: var(--good-soft); transform: scale(1.15); }
.dp3-node.is-target svg { stroke: var(--good); }
.dp3-labelcol { display: flex; flex-direction: column; }
.dp3-node-label { font-family: 'IBM Plex Mono', monospace; font-size: 0.72rem; font-weight: 700; color: var(--ink-soft); transition: color 0.4s ease; }
.dp3-node-label.is-lit { color: var(--accent); }
.dp3-node-label.is-target { color: var(--good); }
.dp3-node-sub { font-family: 'IBM Plex Mono', monospace; font-size: 0.6rem; color: var(--ink-soft); opacity: 0.8; }
.dp3-illus { flex: 1; display: flex; justify-content: flex-end; align-items: center; padding-right: 0.4rem; }
.dp3-illus svg { width: 30px; height: 30px; stroke: var(--line); transition: stroke 0.4s ease, opacity 0.4s ease; opacity: 0.7; }
.dp3-row.is-illus-lit .dp3-illus svg { stroke: var(--accent); opacity: 1; }
.dp3-row.is-illus-target .dp3-illus svg { stroke: var(--good); opacity: 1; }
.dp3-target-badge {
font-family: 'IBM Plex Mono', monospace; font-size: 0.64rem; font-weight: 700; color: var(--good);
opacity: 0; transition: opacity 0.4s ease; white-space: nowrap;
}
.dp3-target-badge.is-shown { opacity: 1; }
.dp3-link { width: 2px; height: 26px; background: var(--line); position: relative; }
.dp3-link-fill { position: absolute; left: 0; top: 0; width: 2px; height: 0%; background: var(--accent); transition: height 0.4s ease; }
.dp3-link-arrow { text-align: center; font-size: 0.78rem; color: var(--line); transition: color 0.4s ease; }
.dp3-link-arrow.is-lit { color: var(--accent); }
.dp3-play { display: flex; align-items: center; justify-content: center; margin: 1.4rem auto 0; border: none; background: var(--accent); color: #fff; width: 50px; height: 50px; border-radius: 50%; cursor: pointer; }
.dp3-play svg { width: 20px; height: 20px; }
.dp3-play:active { transform: scale(0.94); }
.dp3-play:disabled { opacity: 0.45; cursor: default; }
@media (prefers-reduced-motion: reduce) {
.infographic-dompath3 *, .infographic-dompath3 *::before, .infographic-dompath3 *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="dp3-wrap">
<div class="dp3-code"><<span class="tok-tag">html</span>>
<<span class="tok-tag">body</span>>
<<span class="tok-tag">div</span> <span class="tok-attr">id</span>=<span class="tok-aval">"container"</span>>
<<span class="tok-tag">button</span> <span class="tok-attr">id</span>=<span class="tok-aval">"btn"</span>>Click Me</<span class="tok-tag">button</span>>
</<span class="tok-tag">div</span>>
</<span class="tok-tag">body</span>>
</<span class="tok-tag">html</span>></div>
<div class="dp3-chain">
<div class="dp3-row" id="rowDoc">
<div class="dp3-linkcol">
<div class="dp3-node" id="nDoc">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg>
</div>
</div>
<div class="dp3-labelcol">
<div class="dp3-node-label" id="labDoc">Document</div>
</div>
<div class="dp3-illus">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg>
</div>
</div>
<div class="dp3-linkcol">
<div class="dp3-link" id="lk1"><div class="dp3-link-fill"></div></div>
<div class="dp3-link-arrow" id="ar1">β</div>
</div>
<div class="dp3-row" id="rowHtml">
<div class="dp3-linkcol">
<div class="dp3-node" id="nHtml">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 16 4-4-4-4"/><path d="m6 8-4 4 4 4"/><path d="m14.5 4-5 16"/></svg>
</div>
</div>
<div class="dp3-labelcol">
<div class="dp3-node-label" id="labHtml">Html</div>
</div>
<div class="dp3-illus">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="M2 9h20"/></svg>
</div>
</div>
<div class="dp3-linkcol">
<div class="dp3-link" id="lk2"><div class="dp3-link-fill"></div></div>
<div class="dp3-link-arrow" id="ar2">β</div>
</div>
<div class="dp3-row" id="rowBody">
<div class="dp3-linkcol">
<div class="dp3-node" id="nBody">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/></svg>
</div>
</div>
<div class="dp3-labelcol">
<div class="dp3-node-label" id="labBody">Body</div>
</div>
<div class="dp3-illus">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 12h18"/></svg>
</div>
</div>
<div class="dp3-linkcol">
<div class="dp3-link" id="lk3"><div class="dp3-link-fill"></div></div>
<div class="dp3-link-arrow" id="ar3">β</div>
</div>
<div class="dp3-row" id="rowDiv">
<div class="dp3-linkcol">
<div class="dp3-node" id="nDiv">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2"/></svg>
</div>
</div>
<div class="dp3-labelcol">
<div class="dp3-node-label" id="labDiv">Div</div>
<div class="dp3-node-sub">#container</div>
</div>
<div class="dp3-illus">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 3a2 2 0 0 0-2 2"/><path d="M19 3a2 2 0 0 1 2 2"/><path d="M21 19a2 2 0 0 1-2 2"/><path d="M5 21a2 2 0 0 1-2-2"/><path d="M9 3h1"/><path d="M9 21h1"/><path d="M14 3h1"/><path d="M14 21h1"/><path d="M3 9v1"/><path d="M21 9v1"/><path d="M3 14v1"/><path d="M21 14v1"/></svg>
</div>
</div>
<div class="dp3-linkcol">
<div class="dp3-link" id="lk4"><div class="dp3-link-fill"></div></div>
<div class="dp3-link-arrow" id="ar4">β</div>
</div>
<div class="dp3-row" id="rowBtn">
<div class="dp3-linkcol">
<div class="dp3-node" id="nBtn">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.5 21h5"/><path d="M12 3v3"/><path d="M18.5 8.5 16 11"/><path d="M2 12h3"/><path d="M19 12h3"/><path d="m4 4 2.5 2.5"/><path d="M9 17a4 4 0 0 1 6 0"/></svg>
</div>
</div>
<div class="dp3-labelcol">
<div class="dp3-node-label" id="labBtn">Button</div>
<div class="dp3-node-sub">#btn</div>
</div>
<div class="dp3-illus">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 14a8 8 0 0 1-8 8"/><path d="M18 11v-1a2 2 0 0 0-2-2a2 2 0 0 0-2 2"/><path d="M14 10V9a2 2 0 0 0-2-2a2 2 0 0 0-2 2v1"/><path d="M10 9.5V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v10"/><path d="M18 11a2 2 0 1 1 4 0v3a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15"/></svg>
</div>
</div>
<div class="dp3-row">
<div class="dp3-linkcol"></div>
<div class="dp3-target-badge" id="badge">β target</div>
</div>
</div>
<button class="dp3-play" id="dpPlay" type="button" aria-label="play">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M8 5v14l11-7z"/></svg>
</button>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-dompath3')
|| document.querySelector('.infographic-dompath3');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
const rows = ['rowDoc', 'rowHtml', 'rowBody', 'rowDiv', 'rowBtn'].map(id => wrapper.querySelector('#' + id));
const nodes = ['nDoc', 'nHtml', 'nBody', 'nDiv', 'nBtn'].map(id => wrapper.querySelector('#' + id));
const labels = ['labDoc', 'labHtml', 'labBody', 'labDiv', 'labBtn'].map(id => wrapper.querySelector('#' + id));
const links = ['lk1', 'lk2', 'lk3', 'lk4'].map(id => wrapper.querySelector('#' + id + ' .dp3-link-fill'));
const arrows = ['ar1', 'ar2', 'ar3', 'ar4'].map(id => wrapper.querySelector('#' + id));
const badge = wrapper.querySelector('#badge');
const playBtn = wrapper.querySelector('#dpPlay');
playBtn.addEventListener('click', () => {
playBtn.disabled = true;
nodes.forEach(n => n.classList.remove('is-lit', 'is-target'));
labels.forEach(l => l.classList.remove('is-lit', 'is-target'));
rows.forEach(r => r.classList.remove('is-illus-lit', 'is-illus-target'));
links.forEach(l => { l.style.height = '0%'; });
arrows.forEach(a => a.classList.remove('is-lit'));
badge.classList.remove('is-shown');
const STEP = 480;
let delay = 150;
nodes.forEach((node, i) => {
setTimeout(() => {
if (i > 0) { links[i - 1].style.height = '100%'; arrows[i - 1].classList.add('is-lit'); }
if (i === nodes.length - 1) {
node.classList.add('is-target');
labels[i].classList.add('is-target');
rows[i].classList.add('is-illus-target');
} else {
node.classList.add('is-lit');
labels[i].classList.add('is-lit');
rows[i].classList.add('is-illus-lit');
}
}, delay);
delay += STEP;
});
setTimeout(() => {
badge.classList.add('is-shown');
playBtn.disabled = false;
}, delay);
});
})();
</script>π΅ 1. Event Capturing Phase (Trickling Down)
π What happens?
The event starts from the top-most element (
document)Travels downward toward the target element
Also called event trickling
Document β HTML β Body β Div β ButtonπΉ Syntax
element.addEventListener("click", handler, true);π The third argument true enables capturing mode
β Capturing Example
<div class="code-terminal-demo">
<style>
.code-terminal-demo {
--win-red: #ff5f57;
--win-yellow: #febc2e;
--win-green: #28c840;
max-width: 880px;
margin: 0 auto;
font-family: 'Inter', system-ui, sans-serif;
}
.code-terminal-demo * { box-sizing: border-box; }
.ctd-window {
border-radius: 10px;
overflow: hidden;
box-shadow: 0 8px 30px rgba(0,0,0,0.35);
margin-bottom: 1.5rem;
}
.ctd-titlebar {
background: #2b2b2b;
padding: 0.6rem 0.8rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.ctd-dot { width: 11px; height: 11px; border-radius: 50%; }
.ctd-dot.red { background: var(--win-red); }
.ctd-dot.yellow { background: var(--win-yellow); }
.ctd-dot.green { background: var(--win-green); }
.ctd-filename {
flex: 1;
text-align: center;
color: #9a9a9a;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.75rem;
margin-right: 3.4rem;
}
.ctd-code {
background: #1e1e1e;
color: #d4d4d4;
margin: 0;
padding: 1rem 1.1rem;
font-family: 'IBM Plex Mono', 'Fira Code', monospace;
font-size: 0.76rem;
line-height: 1.65;
overflow-x: auto;
white-space: pre;
}
.tok-kw { color: #569cd6; }
.tok-fn { color: #dcdcaa; }
.tok-str { color: #ce9178; }
.tok-var { color: #9cdcfe; }
.tok-punc { color: #d4d4d4; }
.tok-kwv { color: #569cd6; }
.ctd-run {
display: block;
margin: 0 auto 1.5rem;
border: none;
background: #2563eb;
color: #fff;
padding: 0.55rem 1.3rem;
font-family: 'Inter', system-ui, sans-serif;
font-size: 0.85rem;
font-weight: 600;
border-radius: 6px;
cursor: pointer;
}
.ctd-run:active { transform: scale(0.97); }
.ctd-run:disabled { opacity: 0.5; cursor: default; }
.ctd-term {
background: #0c0c0c;
color: #e5e5e5;
padding: 1rem 1.1rem;
font-family: 'IBM Plex Mono', 'Fira Code', monospace;
font-size: 0.78rem;
line-height: 1.9;
}
.ctd-term .line { display: block; opacity: 0; transition: opacity 0.25s ease; }
.ctd-term .line.is-shown { opacity: 1; }
.ctd-term .prompt { color: #6ee7b7; }
.ctd-term .path { color: #7dd3fc; }
.ctd-term .out { color: #e5e5e5; }
.ctd-term .cursor {
display: inline-block;
width: 7px;
height: 1em;
background: #e5e5e5;
vertical-align: text-bottom;
margin-left: 2px;
animation: ctd-blink 1s steps(1) infinite;
}
@keyframes ctd-blink { 50% { opacity: 0; } }
</style>
<div class="ctd-window">
<div class="ctd-titlebar">
<div class="ctd-dot red"></div>
<div class="ctd-dot yellow"></div>
<div class="ctd-dot green"></div>
<div class="ctd-filename">script.js</div>
</div>
<pre class="ctd-code"><span class="tok-var">document</span><span class="tok-punc">.</span><span class="tok-fn">getElementById</span><span class="tok-punc">(</span><span class="tok-str">"container"</span><span class="tok-punc">).</span><span class="tok-fn">addEventListener</span><span class="tok-punc">(</span>
<span class="tok-str">"click"</span><span class="tok-punc">,</span>
<span class="tok-punc">() => {</span>
<span class="tok-var">console</span><span class="tok-punc">.</span><span class="tok-fn">log</span><span class="tok-punc">(</span><span class="tok-str">"Div clicked (Capturing)"</span><span class="tok-punc">);</span>
<span class="tok-punc">},</span>
<span class="tok-kwv">true</span>
<span class="tok-punc">);</span>
<span class="tok-var">document</span><span class="tok-punc">.</span><span class="tok-fn">getElementById</span><span class="tok-punc">(</span><span class="tok-str">"btn"</span><span class="tok-punc">).</span><span class="tok-fn">addEventListener</span><span class="tok-punc">(</span>
<span class="tok-str">"click"</span><span class="tok-punc">,</span>
<span class="tok-punc">() => {</span>
<span class="tok-var">console</span><span class="tok-punc">.</span><span class="tok-fn">log</span><span class="tok-punc">(</span><span class="tok-str">"Button clicked (Capturing)"</span><span class="tok-punc">);</span>
<span class="tok-punc">},</span>
<span class="tok-kwv">true</span>
<span class="tok-punc">);</span></pre>
</div>
<button class="ctd-run" id="ctdRun" type="button">βΆ click #btn</button>
<div class="ctd-window">
<div class="ctd-titlebar">
<div class="ctd-dot red"></div>
<div class="ctd-dot yellow"></div>
<div class="ctd-dot green"></div>
<div class="ctd-filename">console</div>
</div>
<div class="ctd-term" id="ctdTerm">
<span class="line is-shown"><span class="prompt">βΊ</span> <span class="path">user clicks #btn</span></span>
<span class="line out" id="tline1">Div clicked (Capturing)</span>
<span class="line out" id="tline2">Button clicked (Capturing)</span>
<span class="line is-shown" id="tlinePrompt"><span class="prompt">βΊ</span> <span class="cursor"></span></span>
</div>
</div>
</div>
<script>
(function () {
const scope = document.currentScript.closest('.code-terminal-demo') || document;
const runBtn = scope.querySelector('#ctdRun');
const lineIds = ['tline1', 'tline2'];
const promptLine = scope.querySelector('#tlinePrompt');
runBtn.addEventListener('click', () => {
runBtn.disabled = true;
lineIds.forEach(id => scope.querySelector('#' + id).classList.remove('is-shown'));
promptLine.classList.remove('is-shown');
const STEP = 650;
lineIds.forEach((id, i) => {
setTimeout(() => scope.querySelector('#' + id).classList.add('is-shown'), (i + 1) * STEP);
});
setTimeout(() => {
promptLine.classList.add('is-shown');
runBtn.disabled = false;
}, (lineIds.length + 1) * STEP);
});
})();
</script>π Why this order?
Because the event moves from parent β child
π§ When is Capturing Useful?
Security validations
Intercepting events before they reach the target
Complex UI frameworks
Closing Modal or Menu
Preventing certain interactions early
β οΈ Used rarely in normal applications
π― 2. Target Phase
π What happens?
The event reaches the target element
Both capturing and bubbling listeners can run here
π The target phase is unavoidable
π’ 3. Event Bubbling Phase (Default Behavior)
π What happens?
The event starts from the target element
Travels upward through parent elements
This is the default event behavior
Button β Div β Body β HTML β Documentβ Bubbling Example
<div class="code-terminal-demo">
<style>
.code-terminal-demo {
--win-red: #ff5f57;
--win-yellow: #febc2e;
--win-green: #28c840;
max-width: 880px;
margin: 0 auto;
font-family: 'Inter', system-ui, sans-serif;
}
.code-terminal-demo * { box-sizing: border-box; }
.ctd-window {
border-radius: 10px;
overflow: hidden;
box-shadow: 0 8px 30px rgba(0,0,0,0.35);
margin-bottom: 1.5rem;
}
.ctd-titlebar {
background: #2b2b2b;
padding: 0.6rem 0.8rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.ctd-dot { width: 11px; height: 11px; border-radius: 50%; }
.ctd-dot.red { background: var(--win-red); }
.ctd-dot.yellow { background: var(--win-yellow); }
.ctd-dot.green { background: var(--win-green); }
.ctd-filename {
flex: 1;
text-align: center;
color: #9a9a9a;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.75rem;
margin-right: 3.4rem;
}
.ctd-code {
background: #1e1e1e;
color: #d4d4d4;
margin: 0;
padding: 1rem 1.1rem;
font-family: 'IBM Plex Mono', 'Fira Code', monospace;
font-size: 0.76rem;
line-height: 1.65;
overflow-x: auto;
white-space: pre;
}
.tok-kw { color: #569cd6; }
.tok-fn { color: #dcdcaa; }
.tok-str { color: #ce9178; }
.tok-var { color: #9cdcfe; }
.tok-punc { color: #d4d4d4; }
.tok-kwv { color: #569cd6; }
.ctd-run {
display: block;
margin: 0 auto 1.5rem;
border: none;
background: #2563eb;
color: #fff;
padding: 0.55rem 1.3rem;
font-family: 'Inter', system-ui, sans-serif;
font-size: 0.85rem;
font-weight: 600;
border-radius: 6px;
cursor: pointer;
}
.ctd-run:active { transform: scale(0.97); }
.ctd-run:disabled { opacity: 0.5; cursor: default; }
.ctd-term {
background: #0c0c0c;
color: #e5e5e5;
padding: 1rem 1.1rem;
font-family: 'IBM Plex Mono', 'Fira Code', monospace;
font-size: 0.78rem;
line-height: 1.9;
}
.ctd-term .line { display: block; opacity: 0; transition: opacity 0.25s ease; }
.ctd-term .line.is-shown { opacity: 1; }
.ctd-term .prompt { color: #6ee7b7; }
.ctd-term .path { color: #7dd3fc; }
.ctd-term .out { color: #e5e5e5; }
.ctd-term .cursor {
display: inline-block;
width: 7px;
height: 1em;
background: #e5e5e5;
vertical-align: text-bottom;
margin-left: 2px;
animation: ctd-blink 1s steps(1) infinite;
}
@keyframes ctd-blink { 50% { opacity: 0; } }
</style>
<div class="ctd-window">
<div class="ctd-titlebar">
<div class="ctd-dot red"></div>
<div class="ctd-dot yellow"></div>
<div class="ctd-dot green"></div>
<div class="ctd-filename">script.js</div>
</div>
<pre class="ctd-code"><span class="tok-var">document</span><span class="tok-punc">.</span><span class="tok-fn">getElementById</span><span class="tok-punc">(</span><span class="tok-str">"btn"</span><span class="tok-punc">).</span><span class="tok-fn">addEventListener</span><span class="tok-punc">(</span><span class="tok-str">"click"</span><span class="tok-punc">, () => {</span>
<span class="tok-var">console</span><span class="tok-punc">.</span><span class="tok-fn">log</span><span class="tok-punc">(</span><span class="tok-str">"Button clicked"</span><span class="tok-punc">);</span>
<span class="tok-punc">});</span>
<span class="tok-var">document</span><span class="tok-punc">.</span><span class="tok-fn">getElementById</span><span class="tok-punc">(</span><span class="tok-str">"container"</span><span class="tok-punc">).</span><span class="tok-fn">addEventListener</span><span class="tok-punc">(</span><span class="tok-str">"click"</span><span class="tok-punc">, () => {</span>
<span class="tok-var">console</span><span class="tok-punc">.</span><span class="tok-fn">log</span><span class="tok-punc">(</span><span class="tok-str">"Div clicked"</span><span class="tok-punc">);</span>
<span class="tok-punc">});</span>
<span class="tok-var">document</span><span class="tok-punc">.</span><span class="tok-var">body</span><span class="tok-punc">.</span><span class="tok-fn">addEventListener</span><span class="tok-punc">(</span><span class="tok-str">"click"</span><span class="tok-punc">, () => {</span>
<span class="tok-var">console</span><span class="tok-punc">.</span><span class="tok-fn">log</span><span class="tok-punc">(</span><span class="tok-str">"Body clicked"</span><span class="tok-punc">);</span>
<span class="tok-punc">});</span></pre>
</div>
<button class="ctd-run" id="ctdRun" type="button">βΆ click #btn</button>
<div class="ctd-window">
<div class="ctd-titlebar">
<div class="ctd-dot red"></div>
<div class="ctd-dot yellow"></div>
<div class="ctd-dot green"></div>
<div class="ctd-filename">console</div>
</div>
<div class="ctd-term" id="ctdTerm">
<span class="line is-shown"><span class="prompt">βΊ</span> <span class="path">user clicks #btn</span></span>
<span class="line out" id="tline1">Button clicked</span>
<span class="line out" id="tline2">Div clicked</span>
<span class="line out" id="tline3">Body clicked</span>
<span class="line is-shown" id="tlinePrompt"><span class="prompt">βΊ</span> <span class="cursor"></span></span>
</div>
</div>
</div>
<script>
(function () {
const scope = document.currentScript.closest('.code-terminal-demo') || document;
const runBtn = scope.querySelector('#ctdRun');
const lineIds = ['tline1', 'tline2', 'tline3'];
const promptLine = scope.querySelector('#tlinePrompt');
runBtn.addEventListener('click', () => {
runBtn.disabled = true;
lineIds.forEach(id => scope.querySelector('#' + id).classList.remove('is-shown'));
promptLine.classList.remove('is-shown');
const STEP = 650;
lineIds.forEach((id, i) => {
setTimeout(() => scope.querySelector('#' + id).classList.add('is-shown'), (i + 1) * STEP);
});
setTimeout(() => {
promptLine.classList.add('is-shown');
runBtn.disabled = false;
}, (lineIds.length + 1) * STEP);
});
})();
</script>π Why this order?
Because the event bubbles from child β parent
π Event Delegation (Powered by Bubbling)
π What is Event Delegation?
Instead of adding event listeners to many child elements,
add one listener to a parent and handle events using bubbling.
β HTML
<ul id="list">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul><div class="infographic-delegation">
<style>
.infographic-delegation {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #1a8a5f;
--good-soft: #e1f2ea;
--bad: #c8452e;
--bad-soft: #f7e3df;
box-sizing: border-box;
max-width: 880px;
margin: 0 auto;
font-family: 'Inter', system-ui, sans-serif;
color: var(--ink);
background: var(--paper);
overflow-x: hidden;
}
.infographic-delegation[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4fbf8f;
--good-soft: #16281f;
--bad: #e0715a;
--bad-soft: #2e211d;
}
.infographic-delegation * { box-sizing: border-box; }
.infographic-delegation h1 { font-family: 'Fraunces', Georgia, serif; font-weight: 600; margin: 0; }
.infographic-delegation p { margin: 0; }
.infographic-delegation button { font-family: inherit; }
.dl-wrap { padding: clamp(1.25rem, 5vw, 2.5rem) clamp(1.1rem, 5vw, 3rem) 2rem; }
.dl-hero { text-align: center; margin-bottom: 1.2rem; }
.dl-hero h1 { font-size: clamp(1.35rem, 4.5vw, 1.85rem); }
.dl-hero p { color: var(--ink-soft); margin-top: 0.4rem; font-size: 0.84rem; }
.dl-tabs { display: flex; flex-wrap: wrap; gap: 0.4rem; justify-content: center; margin-bottom: 1.2rem; }
.dl-tab { border: 1px solid var(--line); background: var(--paper-alt); color: var(--ink-soft); padding: 0.36rem 0.65rem; font-size: 0.66rem; font-weight: 700; cursor: pointer; font-family: 'IBM Plex Mono', monospace; }
.dl-tab.is-active { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.dl-tab.is-active.bad { border-color: var(--bad); background: var(--bad-soft); color: var(--bad); }
.dl-tab:hover:not(.is-active) { border-color: var(--ink-soft); color: var(--ink); }
.dl-panel { display: none; }
.dl-panel.is-active { display: block; }
.dl-code { background: #1e1e1e; color: #d4d4d4; border-radius: 5px; padding: 0.6rem 0.8rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.68rem; line-height: 1.6; margin-bottom: 1rem; white-space: pre; overflow-x: auto; }
.tok-kw { color: #569cd6; }
.tok-fn { color: #dcdcaa; }
.tok-str { color: #ce9178; }
.tok-var { color: #9cdcfe; }
.tok-punc { color: #d4d4d4; }
.dl-page { border: 1.5px solid var(--line); background: var(--paper-alt); border-radius: 6px; overflow: hidden; margin-bottom: 1rem; }
.dl-page-bar { background: #2b2b2b; padding: 0.4rem 0.6rem; display: flex; gap: 0.3rem; }
.dl-page-dot { width: 8px; height: 8px; border-radius: 50%; background: #555; }
.dl-page-body { padding: 1rem; background: var(--paper); }
/* without delegation */
.dl-btnrow { display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap; }
.dl-btn-unit { display: flex; flex-direction: column; align-items: center; gap: 0.3rem; }
.dl-mini-btn {
border: 1.5px solid var(--line); background: var(--paper); border-radius: 5px; padding: 0.4rem 0.7rem;
font-size: 0.78rem; cursor: pointer; transition: border-color 0.3s ease, background 0.3s ease, transform 0.3s ease;
}
.dl-mini-btn.is-fired { border-color: var(--accent); background: var(--accent-soft); transform: scale(1.06); }
.dl-listen-icon { width: 15px; height: 15px; stroke: var(--bad); opacity: 0.85; }
.dl-listen-label { font-size: 0.56rem; color: var(--bad); font-family: 'IBM Plex Mono', monospace; }
/* with delegation */
.dl-list-container { border: 1.5px solid var(--line); border-radius: 6px; padding: 0.6rem; position: relative; }
.dl-list-container.is-fired { border-color: var(--good); background: var(--good-soft); }
.dl-list-listen { display: flex; align-items: center; justify-content: center; gap: 0.3rem; margin-bottom: 0.5rem; font-size: 0.6rem; color: var(--good); font-family: 'IBM Plex Mono', monospace; }
.dl-list-listen svg { width: 14px; height: 14px; stroke: var(--good); }
.dl-li { border: 1.5px solid var(--line); background: var(--paper); border-radius: 4px; padding: 0.32rem 0.55rem; font-size: 0.8rem; margin-bottom: 0.3rem; cursor: pointer; transition: border-color 0.3s ease, background 0.3s ease; }
.dl-li:last-child { margin-bottom: 0; }
.dl-li.is-clicked { border-color: var(--accent); background: var(--accent-soft); }
.dl-li.is-added { opacity: 0; transform: translateY(-6px) scale(0.95); transition: opacity 0.4s ease, transform 0.4s ease, border-color 0.3s ease, background 0.3s ease; }
.dl-li.is-added.is-shown { opacity: 1; transform: translateY(0) scale(1); }
.dl-bubble-dot { position: absolute; left: 50%; width: 8px; height: 8px; border-radius: 50%; background: var(--accent); opacity: 0; transform: translate(-50%, 0); transition: opacity 0.2s ease; pointer-events: none; }
.dl-console { margin: 1rem auto 0; max-width: 320px; text-align: left; border: 1.5px solid var(--line); background: var(--paper-alt); padding: 0.5rem 0.75rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.7rem; min-height: 1.8em; }
.dl-console .dl-cline { color: var(--good); }
.dl-addbtn { display: block; margin: 0.8rem auto 0; border: 1px solid var(--line); background: var(--paper-alt); color: var(--ink-soft); padding: 0.35rem 0.8rem; font-size: 0.7rem; cursor: pointer; }
.dl-addbtn:hover { border-color: var(--accent); color: var(--accent); }
.dl-note { text-align: center; font-size: 0.78rem; color: var(--ink-soft); margin-top: 1.3rem; }
.dl-note strong { color: var(--ink); }
@media (prefers-reduced-motion: reduce) {
.infographic-delegation *, .infographic-delegation *::before, .infographic-delegation *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="dl-wrap">
<div class="dl-tabs">
<button class="dl-tab is-active bad" data-tab="without" type="button">β without delegation</button>
<button class="dl-tab" data-tab="with" type="button">β
with delegation</button>
</div>
<!-- without -->
<div class="dl-panel is-active" data-panel="without">
<div class="dl-code"><span class="tok-var">buttons</span><span class="tok-punc">.</span><span class="tok-fn">forEach</span><span class="tok-punc">(</span><span class="tok-var">btn</span> <span class="tok-punc">=> {</span>
<span class="tok-var">btn</span><span class="tok-punc">.</span><span class="tok-fn">addEventListener</span><span class="tok-punc">(</span><span class="tok-str">"click"</span><span class="tok-punc">,</span> <span class="tok-var">handleClick</span><span class="tok-punc">);</span>
<span class="tok-punc">});</span></div>
<div class="dl-page">
<div class="dl-page-bar"><div class="dl-page-dot"></div><div class="dl-page-dot"></div><div class="dl-page-dot"></div></div>
<div class="dl-page-body">
<div class="dl-btnrow">
<div class="dl-btn-unit">
<button class="dl-mini-btn" id="wBtn1" type="button">Item 1</button>
<svg class="dl-listen-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/></svg>
<span class="dl-listen-label">listener</span>
</div>
<div class="dl-btn-unit">
<button class="dl-mini-btn" id="wBtn2" type="button">Item 2</button>
<svg class="dl-listen-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/></svg>
<span class="dl-listen-label">listener</span>
</div>
<div class="dl-btn-unit">
<button class="dl-mini-btn" id="wBtn3" type="button">Item 3</button>
<svg class="dl-listen-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/></svg>
<span class="dl-listen-label">listener</span>
</div>
</div>
</div>
</div>
<div class="dl-console"><span class="dl-cline" id="wOut">click a button above</span></div>
</div>
<!-- with -->
<div class="dl-panel" data-panel="with">
<div class="dl-code"><span class="tok-var">document</span><span class="tok-punc">.</span><span class="tok-fn">getElementById</span><span class="tok-punc">(</span><span class="tok-str">"list"</span><span class="tok-punc">).</span><span class="tok-fn">addEventListener</span><span class="tok-punc">(</span><span class="tok-str">"click"</span><span class="tok-punc">, (</span><span class="tok-var">event</span><span class="tok-punc">) => {</span>
<span class="tok-kw">if</span> <span class="tok-punc">(</span><span class="tok-var">event</span><span class="tok-punc">.</span><span class="tok-var">target</span><span class="tok-punc">.</span><span class="tok-var">tagName</span> <span class="tok-punc">===</span> <span class="tok-str">"LI"</span><span class="tok-punc">) {</span>
<span class="tok-var">console</span><span class="tok-punc">.</span><span class="tok-fn">log</span><span class="tok-punc">(</span><span class="tok-var">event</span><span class="tok-punc">.</span><span class="tok-var">target</span><span class="tok-punc">.</span><span class="tok-var">textContent</span><span class="tok-punc">);</span>
<span class="tok-punc">}</span>
<span class="tok-punc">});</span></div>
<div class="dl-page">
<div class="dl-page-bar"><div class="dl-page-dot"></div><div class="dl-page-dot"></div><div class="dl-page-dot"></div></div>
<div class="dl-page-body">
<div class="dl-list-container" id="listContainer">
<div class="dl-list-listen">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/></svg>
one listener β on #list
</div>
<div class="dl-li" data-text="Item 1">Item 1</div>
<div class="dl-li" data-text="Item 2">Item 2</div>
<div class="dl-li" data-text="Item 3">Item 3</div>
<div class="dl-li is-added" data-text="Item 4" id="newLi">Item 4</div>
<div class="dl-bubble-dot" id="bubbleDot"></div>
</div>
</div>
</div>
<button class="dl-addbtn" id="addItemBtn" type="button">+ add "Item 4" dynamically</button>
<div class="dl-console"><span class="dl-cline" id="wOut2">click any item above</span></div>
<p class="dl-note">Click <strong>any</strong> <li>, even one added after the page loaded β the listener on the parent still catches it via bubbling.</p>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-delegation')
|| document.querySelector('.infographic-delegation');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
wrapper.querySelectorAll('.dl-tab').forEach(tab => {
tab.addEventListener('click', () => {
wrapper.querySelectorAll('.dl-tab').forEach(t => t.classList.remove('is-active'));
wrapper.querySelectorAll('.dl-panel').forEach(p => p.classList.remove('is-active'));
tab.classList.add('is-active');
wrapper.querySelector('.dl-panel[data-panel="' + tab.dataset.tab + '"]').classList.add('is-active');
});
});
/* without delegation */
const wOut = wrapper.querySelector('#wOut');
['wBtn1', 'wBtn2', 'wBtn3'].forEach((id, i) => {
const btn = wrapper.querySelector('#' + id);
btn.addEventListener('click', () => {
wrapper.querySelectorAll('.dl-mini-btn').forEach(b => b.classList.remove('is-fired'));
btn.classList.add('is-fired');
wOut.textContent = 'handleClick() fired β from Item ' + (i + 1) + '\u2019s own listener';
});
});
/* with delegation */
const listContainer = wrapper.querySelector('#listContainer');
const wOut2 = wrapper.querySelector('#wOut2');
const bubbleDot = wrapper.querySelector('#bubbleDot');
const addBtn = wrapper.querySelector('#addItemBtn');
const newLi = wrapper.querySelector('#newLi');
function wireLi(li) {
li.addEventListener('click', () => {
wrapper.querySelectorAll('.dl-li').forEach(el => el.classList.remove('is-clicked'));
li.classList.add('is-clicked');
const rect = li.getBoundingClientRect();
const containerRect = listContainer.getBoundingClientRect();
bubbleDot.style.top = (rect.top - containerRect.top + rect.height / 2) + 'px';
bubbleDot.style.opacity = '1';
bubbleDot.style.transition = 'none';
void bubbleDot.offsetWidth;
bubbleDot.style.transition = 'top 0.4s ease, opacity 0.3s ease 0.3s';
requestAnimationFrame(() => {
bubbleDot.style.top = '6px';
setTimeout(() => { bubbleDot.style.opacity = '0'; }, 400);
});
setTimeout(() => {
listContainer.classList.add('is-fired');
wOut2.textContent = '"' + li.dataset.text + '"';
setTimeout(() => listContainer.classList.remove('is-fired'), 500);
}, 420);
});
}
wrapper.querySelectorAll('.dl-li').forEach(wireLi);
addBtn.addEventListener('click', () => {
newLi.classList.add('is-shown');
addBtn.disabled = true;
addBtn.textContent = '"Item 4" added β try clicking it';
});
})();
</script>π‘ Benefits
β Better performance
β Works with dynamically added elements
β Cleaner code
β Stopping Event Flow
πΉ event.stopPropagation()
Stops the event from moving further up or down
<div class="infographic-stopprop">
<style>
.infographic-stopprop {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #1a8a5f;
--good-soft: #e1f2ea;
--bad: #c8452e;
--bad-soft: #f7e3df;
box-sizing: border-box;
max-width: 880px;
margin: 0 auto;
font-family: 'Inter', system-ui, sans-serif;
color: var(--ink);
background: var(--paper);
overflow-x: hidden;
}
.infographic-stopprop[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4fbf8f;
--good-soft: #16281f;
--bad: #e0715a;
--bad-soft: #2e211d;
}
.infographic-stopprop * { box-sizing: border-box; }
.infographic-stopprop h1 { font-family: 'Fraunces', Georgia, serif; font-weight: 600; margin: 0; }
.infographic-stopprop p { margin: 0; }
.infographic-stopprop button { font-family: inherit; }
.sp-wrap { padding: clamp(1.25rem, 5vw, 2.5rem) clamp(1.1rem, 5vw, 3rem) 2rem; }
.sp-hero { text-align: center; margin-bottom: 1.2rem; }
.sp-hero h1 { font-size: clamp(1.3rem, 4.5vw, 1.75rem); }
.sp-hero p { color: var(--ink-soft); margin-top: 0.4rem; font-size: 0.84rem; }
.sp-code { background: #1e1e1e; color: #d4d4d4; border-radius: 5px; padding: 0.6rem 0.8rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.68rem; line-height: 1.6; margin-bottom: 1.2rem; white-space: pre; overflow-x: auto; }
.tok-kw { color: #569cd6; }
.tok-fn { color: #dcdcaa; }
.tok-str { color: #ce9178; }
.tok-var { color: #9cdcfe; }
.tok-punc { color: #d4d4d4; }
.sp-code #hlLine.hl { background: rgba(200,69,46,0.25); border-radius: 3px; padding: 0.05rem 0.15rem; }
.sp-toggle-row { display: flex; align-items: center; justify-content: center; gap: 0.6rem; margin-bottom: 1.3rem; }
.sp-toggle-label { font-family: 'IBM Plex Mono', monospace; font-size: 0.72rem; color: var(--ink-soft); }
.sp-toggle { display: flex; border: 1.5px solid var(--line); overflow: hidden; }
.sp-toggle button { border: none; background: var(--paper-alt); color: var(--ink-soft); padding: 0.32rem 0.7rem; font-size: 0.68rem; font-weight: 700; cursor: pointer; font-family: 'IBM Plex Mono', monospace; }
.sp-toggle button.on.is-active { background: var(--good-soft); color: var(--good); }
.sp-toggle button.off.is-active { background: var(--bad-soft); color: var(--bad); }
.sp-nest { border: 1.5px solid var(--line); border-radius: 8px; padding: 1rem; background: var(--paper-alt); transition: border-color 0.35s ease, background 0.35s ease; position: relative; }
.sp-nest-label { position: absolute; top: -9px; left: 10px; background: var(--paper); padding: 0 0.4rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.6rem; color: var(--ink-soft); display: flex; align-items: center; gap: 0.25rem; transition: color 0.35s ease; }
.sp-nest-label svg { width: 11px; height: 11px; stroke: var(--ink-soft); transition: stroke 0.35s ease; }
.sp-nest.is-lit { border-color: var(--accent); background: var(--accent-soft); }
.sp-nest.is-lit .sp-nest-label { color: var(--accent); }
.sp-nest.is-lit .sp-nest-label svg { stroke: var(--accent); }
.sp-nest.is-blocked { border-color: var(--bad); background: var(--bad-soft); opacity: 0.5; }
.sp-target-btn {
display: block; margin: 0.4rem auto 0; border: 1.5px solid var(--line); background: var(--paper);
color: var(--ink); padding: 0.5rem 1rem; font-size: 0.8rem; font-weight: 600; border-radius: 6px; cursor: pointer;
transition: border-color 0.35s ease, background 0.35s ease, color 0.35s ease, transform 0.35s ease;
}
.sp-target-btn.is-lit { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); transform: scale(1.05); }
.sp-stop-badge {
display: block; width: fit-content; margin: 0.5rem auto 0; font-family: 'IBM Plex Mono', monospace; font-size: 0.6rem; font-weight: 700;
color: var(--bad); border: 1.5px solid var(--bad); background: var(--bad-soft); padding: 0.15rem 0.45rem; opacity: 0; transition: opacity 0.3s ease;
}
.sp-stop-badge.is-shown { opacity: 1; }
.sp-console { margin: 1.2rem auto 0; max-width: 320px; text-align: left; border: 1.5px solid var(--line); background: var(--paper-alt); padding: 0.55rem 0.8rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.7rem; min-height: 3.6em; line-height: 1.6; }
.sp-console .sp-cline { display: block; opacity: 0; transition: opacity 0.25s ease; color: var(--good); }
.sp-console .sp-cline.is-shown { opacity: 1; }
.sp-note { text-align: center; font-size: 0.8rem; color: var(--ink-soft); margin-top: 1.3rem; }
.sp-note strong { color: var(--ink); }
@media (prefers-reduced-motion: reduce) {
.infographic-stopprop *, .infographic-stopprop *::before, .infographic-stopprop *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="sp-wrap">
<div class="sp-code" id="spCode"><span class="tok-var">button</span><span class="tok-punc">.</span><span class="tok-fn">addEventListener</span><span class="tok-punc">(</span><span class="tok-str">"click"</span><span class="tok-punc">, (</span><span class="tok-var">event</span><span class="tok-punc">) => {</span>
<span id="hlLine"><span class="tok-var">event</span><span class="tok-punc">.</span><span class="tok-fn">stopPropagation</span><span class="tok-punc">();</span></span>
<span class="tok-var">console</span><span class="tok-punc">.</span><span class="tok-fn">log</span><span class="tok-punc">(</span><span class="tok-str">"Only button clicked"</span><span class="tok-punc">);</span>
<span class="tok-punc">});</span></div>
<div class="sp-toggle-row">
<span class="sp-toggle-label">call stopPropagation()?</span>
<div class="sp-toggle" id="spToggle">
<button class="off is-active" data-val="off" type="button">no</button>
<button class="on" data-val="on" type="button">yes</button>
</div>
</div>
<div class="sp-nest" id="nestBody">
<div class="sp-nest-label"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/></svg>body</div>
<div class="sp-nest" id="nestDiv">
<div class="sp-nest-label"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/></svg>div#container</div>
<button class="sp-target-btn" id="targetBtn" type="button">button β click me</button>
</div>
</div>
<div class="sp-stop-badge" id="stopBadge">β propagation stopped here</div>
<div class="sp-console" id="spConsole">
<span class="sp-cline" id="l1">Only button clicked</span>
<span class="sp-cline" id="l2">Div clicked</span>
<span class="sp-cline" id="l3">Body clicked</span>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-stopprop')
|| document.querySelector('.infographic-stopprop');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
let willStop = false;
const toggle = wrapper.querySelector('#spToggle');
const hlLine = wrapper.querySelector('#hlLine');
toggle.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
willStop = b.dataset.val === 'on';
toggle.querySelectorAll('button').forEach(x => x.classList.remove('is-active'));
b.classList.add('is-active');
hlLine.classList.toggle('hl', willStop);
});
});
const targetBtn = wrapper.querySelector('#targetBtn');
const nestDiv = wrapper.querySelector('#nestDiv');
const nestBody = wrapper.querySelector('#nestBody');
const stopBadge = wrapper.querySelector('#stopBadge');
const l1 = wrapper.querySelector('#l1');
const l2 = wrapper.querySelector('#l2');
const l3 = wrapper.querySelector('#l3');
targetBtn.addEventListener('click', () => {
[targetBtn, nestDiv, nestBody].forEach(el => el.classList.remove('is-lit', 'is-blocked'));
stopBadge.classList.remove('is-shown');
[l1, l2, l3].forEach(l => l.classList.remove('is-shown'));
const STEP = 500;
let delay = 60;
setTimeout(() => { targetBtn.classList.add('is-lit'); l1.classList.add('is-shown'); }, delay);
delay += STEP;
if (willStop) {
setTimeout(() => { stopBadge.classList.add('is-shown'); }, delay);
} else {
setTimeout(() => { nestDiv.classList.add('is-lit'); l2.classList.add('is-shown'); }, delay);
delay += STEP;
setTimeout(() => { nestBody.classList.add('is-lit'); l3.classList.add('is-shown'); }, delay);
}
});
})();
</script>Only button clickedπ Capturing vs Bubbling
Feature | Capturing | Bubbling |
|---|---|---|
Direction | Top β Down | Bottom β Up |
Default | β No | β Yes |
Common Usage | Rare | Very Common |
Used For | Early interception | Event delegation |
β Ways to Handle Events
1οΈβ£ Inline HTML Event (β Avoid)
<button onclick="alert('Clicked')">Click</button>β Mixes HTML and JS
β Hard to maintain
2οΈβ£ DOM Property Method
const btn = document.querySelector('button');
btn.onclick = () => {
alert("Clicked");
};β οΈ Only one handler allowed
New assignment overwrites old one
3οΈβ£ addEventListener() (β
Best Practice)
btn.addEventListener("click", () => {
alert("Clicked");
});βοΈ Multiple handlers allowed
βοΈ Supports bubbling & capturing
βοΈ Clean separation of logic
<div class="infographic-eventhandlers">
<style>
.infographic-eventhandlers {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #1a8a5f;
--good-soft: #e1f2ea;
--bad: #c8452e;
--bad-soft: #f7e3df;
box-sizing: border-box;
max-width: 880px;
margin: 0 auto;
font-family: 'Inter', system-ui, sans-serif;
color: var(--ink);
background: var(--paper);
overflow-x: hidden;
}
.infographic-eventhandlers[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4fbf8f;
--good-soft: #16281f;
--bad: #e0715a;
--bad-soft: #2e211d;
}
.infographic-eventhandlers * { box-sizing: border-box; }
.infographic-eventhandlers h1 { font-family: 'Fraunces', Georgia, serif; font-weight: 600; margin: 0; }
.infographic-eventhandlers p { margin: 0; }
.infographic-eventhandlers button { font-family: inherit; }
.eh-wrap { padding: clamp(1.25rem, 5vw, 2.5rem) clamp(1.1rem, 5vw, 3rem) 2rem; }
.eh-hero { text-align: center; margin-bottom: 1.2rem; }
.eh-hero h1 { font-size: clamp(1.35rem, 4.5vw, 1.85rem); }
.eh-hero p { color: var(--ink-soft); margin-top: 0.4rem; font-size: 0.84rem; }
.eh-tabs { display: flex; flex-wrap: wrap; gap: 0.4rem; justify-content: center; margin-bottom: 1.2rem; }
.eh-tab { border: 1px solid var(--line); background: var(--paper-alt); color: var(--ink-soft); padding: 0.36rem 0.65rem; font-size: 0.66rem; font-weight: 700; cursor: pointer; font-family: 'IBM Plex Mono', monospace; }
.eh-tab.is-active { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.eh-tab.is-active.bad { border-color: var(--bad); background: var(--bad-soft); color: var(--bad); }
.eh-tab.is-active.good { border-color: var(--good); background: var(--good-soft); color: var(--good); }
.eh-tab:hover:not(.is-active) { border-color: var(--ink-soft); color: var(--ink); }
.eh-panel { display: none; }
.eh-panel.is-active { display: block; }
.eh-code { background: #1e1e1e; color: #d4d4d4; border-radius: 5px; padding: 0.6rem 0.8rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.68rem; line-height: 1.6; margin-bottom: 1rem; white-space: pre; overflow-x: auto; }
.tok-kw { color: #569cd6; }
.tok-fn { color: #dcdcaa; }
.tok-str { color: #ce9178; }
.tok-var { color: #9cdcfe; }
.tok-punc { color: #d4d4d4; }
.tok-tag { color: #569cd6; }
.tok-attr { color: #9cdcfe; }
.tok-aval { color: #ce9178; }
.eh-page { border: 1.5px solid var(--line); background: var(--paper-alt); border-radius: 6px; overflow: hidden; margin-bottom: 1rem; }
.eh-page-bar { background: #2b2b2b; padding: 0.4rem 0.6rem; display: flex; gap: 0.3rem; }
.eh-page-dot { width: 8px; height: 8px; border-radius: 50%; background: #555; }
.eh-page-body { padding: 1.1rem; background: var(--paper); text-align: center; }
.eh-demo-btn { border: 1.5px solid var(--line); background: var(--paper-alt); border-radius: 6px; padding: 0.5rem 1.1rem; font-size: 0.82rem; cursor: pointer; transition: border-color 0.3s ease, background 0.3s ease, transform 0.3s ease; }
.eh-demo-btn.is-fired { border-color: var(--accent); background: var(--accent-soft); transform: scale(1.05); }
.eh-handlers { display: flex; justify-content: center; gap: 0.5rem; margin-top: 0.8rem; flex-wrap: wrap; min-height: 1.6em; }
.eh-handler-chip {
font-family: 'IBM Plex Mono', monospace; font-size: 0.64rem; padding: 0.22rem 0.5rem; border: 1.5px solid var(--line);
color: var(--ink-soft); opacity: 0; transform: scale(0.85); transition: opacity 0.3s ease, transform 0.3s ease, border-color 0.3s ease, background 0.3s ease, text-decoration 0.3s ease;
}
.eh-handler-chip.is-shown { opacity: 1; transform: scale(1); border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.eh-handler-chip.is-overwritten { border-color: var(--bad); background: var(--bad-soft); color: var(--bad); text-decoration: line-through; opacity: 0.6; }
.eh-controls { display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap; margin-top: 0.9rem; }
.eh-btn2 { border: 1px solid var(--line); background: var(--paper-alt); color: var(--ink); padding: 0.38rem 0.75rem; font-size: 0.72rem; cursor: pointer; }
.eh-btn2:hover { border-color: var(--accent); color: var(--accent); }
.eh-btn2.primary { border: none; background: var(--accent); color: #fff; font-weight: 600; }
.eh-console { margin: 1rem auto 0; max-width: 300px; text-align: left; border: 1.5px solid var(--line); background: var(--paper-alt); padding: 0.5rem 0.75rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.7rem; min-height: 3em; line-height: 1.6; }
.eh-console .eh-cline { display: block; opacity: 0; transition: opacity 0.25s ease; color: var(--good); }
.eh-console .eh-cline.is-shown { opacity: 1; }
.eh-note { text-align: center; font-size: 0.78rem; color: var(--ink-soft); padding: 1.3rem; }
.eh-note strong { color: var(--ink); }
@media (prefers-reduced-motion: reduce) {
.infographic-eventhandlers *, .infographic-eventhandlers *::before, .infographic-eventhandlers *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="eh-wrap">
<div class="eh-tabs">
<button class="eh-tab is-active bad" data-tab="inline" type="button">β inline HTML</button>
<button class="eh-tab" data-tab="onclick" type="button">β οΈ .onclick</button>
<button class="eh-tab good" data-tab="ael" type="button">β
addEventListener</button>
</div>
<!-- inline -->
<div class="eh-panel is-active" data-panel="inline">
<div class="eh-code"><<span class="tok-tag">button</span> <span class="tok-attr">onclick</span>=<span class="tok-aval">"alert('Clicked')"</span>>Click</<span class="tok-tag">button</span>></div>
<div class="eh-page">
<div class="eh-page-bar"><div class="eh-page-dot"></div><div class="eh-page-dot"></div><div class="eh-page-dot"></div></div>
<div class="eh-page-body">
<button class="eh-demo-btn" id="inlineBtn" type="button">Click</button>
</div>
</div>
<div class="eh-console"><span class="eh-cline" id="inlineOut">alert("Clicked")</span></div>
<p class="eh-note">The click logic lives <strong>inside the HTML tag itself</strong> β markup and behavior tangled together.</p>
</div>
<!-- onclick property -->
<div class="eh-panel" data-panel="onclick">
<div class="eh-code"><span class="tok-var">btn</span><span class="tok-punc">.</span><span class="tok-var">onclick</span> <span class="tok-punc">=</span> <span class="tok-punc">() => {</span> <span class="tok-fn">alert</span><span class="tok-punc">(</span><span class="tok-str">"Handler A"</span><span class="tok-punc">);</span> <span class="tok-punc">};</span>
<span class="tok-var">btn</span><span class="tok-punc">.</span><span class="tok-var">onclick</span> <span class="tok-punc">=</span> <span class="tok-punc">() => {</span> <span class="tok-fn">alert</span><span class="tok-punc">(</span><span class="tok-str">"Handler B"</span><span class="tok-punc">);</span> <span class="tok-punc">};</span></div>
<div class="eh-page">
<div class="eh-page-bar"><div class="eh-page-dot"></div><div class="eh-page-dot"></div><div class="eh-page-dot"></div></div>
<div class="eh-page-body">
<button class="eh-demo-btn" id="onclickBtn" type="button">Click</button>
<div class="eh-handlers">
<span class="eh-handler-chip" id="chipA">Handler A</span>
<span class="eh-handler-chip" id="chipB">Handler B</span>
</div>
</div>
</div>
<div class="eh-controls">
<button class="eh-btn2" id="assignA" type="button">btn.onclick = Handler A</button>
<button class="eh-btn2" id="assignB" type="button">btn.onclick = Handler B</button>
</div>
<div class="eh-console"><span class="eh-cline" id="onclickOut">click the button above</span></div>
<p class="eh-note">Assigning a second time <strong>overwrites</strong> the first β only one handler can ever be attached this way.</p>
</div>
<!-- addEventListener -->
<div class="eh-panel" data-panel="ael">
<div class="eh-code"><span class="tok-var">btn</span><span class="tok-punc">.</span><span class="tok-fn">addEventListener</span><span class="tok-punc">(</span><span class="tok-str">"click"</span><span class="tok-punc">,</span> <span class="tok-punc">() => {</span> <span class="tok-fn">alert</span><span class="tok-punc">(</span><span class="tok-str">"Handler A"</span><span class="tok-punc">);</span> <span class="tok-punc">});</span>
<span class="tok-var">btn</span><span class="tok-punc">.</span><span class="tok-fn">addEventListener</span><span class="tok-punc">(</span><span class="tok-str">"click"</span><span class="tok-punc">,</span> <span class="tok-punc">() => {</span> <span class="tok-fn">alert</span><span class="tok-punc">(</span><span class="tok-str">"Handler B"</span><span class="tok-punc">);</span> <span class="tok-punc">});</span></div>
<div class="eh-page">
<div class="eh-page-bar"><div class="eh-page-dot"></div><div class="eh-page-dot"></div><div class="eh-page-dot"></div></div>
<div class="eh-page-body">
<button class="eh-demo-btn" id="aelBtn" type="button">Click</button>
<div class="eh-handlers">
<span class="eh-handler-chip" id="chipA2">Handler A</span>
<span class="eh-handler-chip" id="chipB2">Handler B</span>
</div>
</div>
</div>
<div class="eh-controls">
<button class="eh-btn2 primary" id="addA" type="button">+ addEventListener(A)</button>
<button class="eh-btn2 primary" id="addB" type="button">+ addEventListener(B)</button>
</div>
<div class="eh-console">
<span class="eh-cline" id="aelOutA">Handler A</span>
<span class="eh-cline" id="aelOutB">Handler B</span>
</div>
<p class="eh-note">Both <strong>stack</strong> β click the button and every attached handler runs, in the order it was added.</p>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-eventhandlers')
|| document.querySelector('.infographic-eventhandlers');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
wrapper.querySelectorAll('.eh-tab').forEach(tab => {
tab.addEventListener('click', () => {
wrapper.querySelectorAll('.eh-tab').forEach(t => t.classList.remove('is-active'));
wrapper.querySelectorAll('.eh-panel').forEach(p => p.classList.remove('is-active'));
tab.classList.add('is-active');
wrapper.querySelector('.eh-panel[data-panel="' + tab.dataset.tab + '"]').classList.add('is-active');
});
});
/* inline */
const inlineBtn = wrapper.querySelector('#inlineBtn');
const inlineOut = wrapper.querySelector('#inlineOut');
inlineBtn.addEventListener('click', () => {
inlineBtn.classList.remove('is-fired');
void inlineBtn.offsetWidth;
inlineBtn.classList.add('is-fired');
inlineOut.classList.remove('is-shown');
void inlineOut.offsetWidth;
inlineOut.classList.add('is-shown');
});
/* onclick property β overwrite behavior */
let currentHandler = null;
const onclickBtn = wrapper.querySelector('#onclickBtn');
const chipA = wrapper.querySelector('#chipA');
const chipB = wrapper.querySelector('#chipB');
const onclickOut = wrapper.querySelector('#onclickOut');
wrapper.querySelector('#assignA').addEventListener('click', () => {
chipB.classList.remove('is-shown', 'is-overwritten');
chipA.classList.remove('is-overwritten');
chipA.classList.add('is-shown');
currentHandler = 'A';
});
wrapper.querySelector('#assignB').addEventListener('click', () => {
if (chipA.classList.contains('is-shown')) chipA.classList.add('is-overwritten');
chipB.classList.remove('is-overwritten');
chipB.classList.add('is-shown');
currentHandler = 'B';
});
onclickBtn.addEventListener('click', () => {
onclickBtn.classList.remove('is-fired');
void onclickBtn.offsetWidth;
onclickBtn.classList.add('is-fired');
onclickOut.classList.remove('is-shown');
onclickOut.textContent = currentHandler ? 'alert("Handler ' + currentHandler + '")' : 'no handler assigned yet';
void onclickOut.offsetWidth;
onclickOut.classList.add('is-shown');
});
/* addEventListener β stacking behavior */
let hasA = false, hasB = false;
const aelBtn = wrapper.querySelector('#aelBtn');
const chipA2 = wrapper.querySelector('#chipA2');
const chipB2 = wrapper.querySelector('#chipB2');
const aelOutA = wrapper.querySelector('#aelOutA');
const aelOutB = wrapper.querySelector('#aelOutB');
wrapper.querySelector('#addA').addEventListener('click', () => { hasA = true; chipA2.classList.add('is-shown'); });
wrapper.querySelector('#addB').addEventListener('click', () => { hasB = true; chipB2.classList.add('is-shown'); });
aelBtn.addEventListener('click', () => {
aelBtn.classList.remove('is-fired');
void aelBtn.offsetWidth;
aelBtn.classList.add('is-fired');
aelOutA.classList.remove('is-shown');
aelOutB.classList.remove('is-shown');
if (hasA) setTimeout(() => aelOutA.classList.add('is-shown'), 80);
if (hasB) setTimeout(() => aelOutB.classList.add('is-shown'), hasA ? 350 : 80);
});
})();
</script>