Event capturing and bubbling

이벤트 캡처링과 버블링

A click travels from ancestors to its target and then bubbles back up.

···
html
<div class="v eventflow"><header>EVENT PATH <span id="event-phase">capture</span></header><div class="stage event-stage"><div class="event-nest" id="root"><div id="parent"><button id="child">click target</button></div></div><div class="event-order"><span id="event0">root ↓</span><span id="event1">parent ↓</span><span id="event2">target</span><span id="event3">parent ↑</span><span id="event4">root ↑</span></div></div><div class="muted">capture ↓ · target · bubble ↑</div></div>
css
.v{width:min(94vw,760px);height:min(88vh,320px);padding:clamp(9px,2.4vmin,18px);border:1px solid var(--line);border-radius:12px;background:var(--surface);font:500 clamp(12px,2.4vmin,16px)/1.3 var(--font-sans,system-ui,sans-serif);display:flex;flex-direction:column;gap:clamp(5px,1.4vmin,10px);overflow:hidden}.v header{display:flex;align-items:center;justify-content:space-between;gap:8px;color:var(--accent);font-weight:700}.v header span{color:var(--muted);font-weight:500}.v .stage{flex:1;min-height:0;position:relative}.v .muted{color:var(--muted)}.v .active{background:color-mix(in srgb,var(--accent) 18%,var(--surface));border-color:var(--accent)!important;color:var(--fg)}.v button{font:inherit;color:var(--fg);background:var(--bg);border:1px solid var(--line);border-radius:7px;padding:5px 8px;cursor:pointer}.event-stage{display:flex;align-items:center;justify-content:center;gap:9px}.event-nest{width:55%;height:85%;border:2px solid var(--accent-2);border-radius:9px;padding:8px;display:grid;place-items:center;background:color-mix(in srgb,var(--accent-2) 10%,var(--surface))}.event-nest #parent{width:82%;height:75%;border:2px solid var(--accent);border-radius:8px;display:grid;place-items:center;background:var(--surface)}.event-nest button{font-size:clamp(12px,2.2vmin,13px);white-space:nowrap}.event-order{display:flex;flex-direction:column;gap:2px}.event-order span{font-size:clamp(12px,2vmin,12px);color:var(--muted);padding:1px 4px;border-radius:4px}.event-order span.on{color:var(--accent);background:color-mix(in srgb,var(--accent) 15%,var(--surface));font-weight:700}
js
const root=document.getElementById("root"),parent=document.getElementById("parent"),child=document.getElementById("child"),order=[];root.addEventListener("click",()=>order.push("root down"),true);parent.addEventListener("click",()=>order.push("parent down"),true);child.addEventListener("click",()=>order.push("target"));parent.addEventListener("click",()=>order.push("parent up"));root.addEventListener("click",()=>order.push("root up"));let step=0;function show(){if(step===0){order.length=0;child.click()}for(let j=0;j<5;j++)document.getElementById("event"+j).classList.toggle("on",j===step);document.getElementById("event-phase").textContent=step<2?"capture":step===2?"target":"bubble";step=(step+1)%order.length}show();setInterval(show,450);child.addEventListener("pointerdown",()=>{step=0})

DOM event propagation has a capture phase from ancestors to the target and a bubble phase back to ancestors. A listener registered with the capture option runs on the downward path.

The demo replays the order recorded by real click listeners on nested elements. Keep the phases distinct when using event delegation or stopping propagation.

When to use

Use it to understand listener order when both parent and child handle clicks.

Open as page ↗