When you click an element, the event does not just fire on that element. It starts at the top of the tree and travels down to the target (the capture phase), fires at the target itself (the target phase), and then travels back up to the top (the bubble phase). By default, listeners you add run in the bubble phase, which is why a click on a child also triggers handlers on its ancestors. Adding a listener in capture mode makes it run on the way down instead.
Calling stopPropagation inside a listener stops the event from travelling any further, so listeners on the remaining layers never run. It does not stop other listeners on the same element — stopImmediatePropagation does that. Pick a stop point above to see the trigger order get cut short.
Common questions and answers about this topic.
They are two directions of the same journey. Capture runs top-down, from the outermost ancestor toward the target, before the target is reached. Bubble runs bottom-up, from the target back toward the outermost ancestor. A listener added the normal way runs in the bubble phase; passing true as the third argument to addEventListener runs it in the capture phase instead.
Because of bubbling. After the event fires on the child (the target), it travels back up through every ancestor, running each one's bubble-phase listener along the way. This is often useful — it lets one handler on a container respond to clicks on any child — but it surprises people who expect the event to stay on the element they clicked.
Use it sparingly, when a child needs to handle an event and genuinely must prevent an ancestor from also reacting — for example a button inside a clickable card that should not trigger the card's click. Reaching for it too often usually means the event design could be simpler; it can also break other code that legitimately relies on the event reaching a shared ancestor.