イベントフローとは?

JavaScriptのイベントフローとは、クリックやキー入力などのイベントが発生したときに、そのイベントがDOMツリー内をどのような順番で伝わるかを表す仕組みです。
イベントは単にクリックされた要素だけで処理されるわけではなく、親要素や子要素にも伝わります。この伝わる流れを理解しておくことで、複雑な画面でも意図したイベント処理を実装できるようになります。
キャプチャリングとバブリングの概要
イベントフローには、次の3つのフェーズがあります。
- キャプチャリングフェーズ(外側から内側へ)
- ターゲットフェーズ(イベントが発生した要素)
- バブリングフェーズ(内側から外側へ)
通常、addEventListener()でイベントを登録すると、バブリングフェーズでイベントが処理されます。
document.querySelector("div").addEventListener("click", function () {
console.log("Div clicked");
}, true); // キャプチャリング
document.querySelector("button").addEventListener("click", function () {
console.log("Button clicked");
}); // バブリング(falseは省略可能)
この例では、divはキャプチャリングフェーズ、buttonはバブリングフェーズでイベントを受け取ります。
イベントフローのフェーズ
イベントは次の順番で処理されます。
| フェーズ | イベントの流れ |
|---|---|
| キャプチャリングフェーズ | 最も外側の要素からイベント発生要素へ向かう |
| ターゲットフェーズ | イベントが発生した要素自身で処理される |
| バブリングフェーズ | イベント発生要素から親要素へ向かって伝わる |
イベントフローのイメージ
例えば次のようなHTMLがあるとします。
<div>
<button>クリック</button>
</div>
ボタンをクリックした場合のイベントの流れは次のようになります。
- document
- html
- body
- div
- button(ターゲット)
- div
- body
- html
- document
前半がキャプチャリングフェーズ、後半がバブリングフェーズです。
キャプチャリングフェーズの詳細
キャプチャリングフェーズでは、イベントが外側から内側へ向かって伝わります。
キャプチャリングでイベントを受け取るには、第3引数にtrue、またはオプションで{ capture: true }を指定します。
const div = document.querySelector("div");
const button = document.querySelector("button");
div.addEventListener("click", function () {
console.log("Div (capture)");
}, true);
button.addEventListener("click", function () {
console.log("Button (capture)");
}, true);
実行結果(buttonをクリック)
Div (capture)
Button (capture)
外側の要素から順番にイベントが処理されていることが分かります。
バブリングフェーズの詳細
バブリングフェーズでは、イベントが内側から外側へ向かって伝わります。
第3引数を省略した場合やfalseを指定した場合はこちらになります。
const div = document.querySelector("div");
const button = document.querySelector("button");
div.addEventListener("click", function () {
console.log("Div (bubble)");
});
button.addEventListener("click", function () {
console.log("Button (bubble)");
});
実行結果(buttonをクリック)
Button (bubble)
Div (bubble)
イベントが内側から親要素へ伝わっていることが確認できます。
イベントフローの管理
イベントの伝播は、event.stopPropagation()で停止できます。
const button = document.querySelector("button");
const div = document.querySelector("div");
button.addEventListener("click", function (event) {
console.log("Button clicked");
event.stopPropagation();
});
div.addEventListener("click", function () {
console.log("Div clicked");
});
実行結果
Button clicked
stopPropagation()を呼び出したため、親要素のイベントは実行されません。
captureオプションを使う書き方
現在では、第3引数に真偽値を指定するよりも、オプションオブジェクトを使用する書き方が推奨されています。
element.addEventListener("click", handleClick, {
capture: true
});
この書き方であれば、onceやpassiveなど他のオプションも同時に指定できます。
初心者が間違えやすいポイント
addEventListener()は、第3引数を省略するとバブリングフェーズになる。- キャプチャリングが必要になる場面は多くないため、通常は省略して問題ない。
stopPropagation()はイベントの伝播だけを止める。リンクの遷移やフォーム送信などの既定動作は止まらない。- 既定動作も止めたい場合は
event.preventDefault()を使用する。
まとめ
- イベントフローは、イベントがDOM内を伝わる順番を表す仕組みである。
- イベントは「キャプチャリング → ターゲット → バブリング」の順で処理される。
- 通常の
addEventListener()はバブリングフェーズで実行される。 - キャプチャリングで処理したい場合は
capture: trueまたは第3引数にtrueを指定する。 stopPropagation()を使うと親要素へのイベント伝播を停止できる。- イベントの既定動作を止めるには
preventDefault()を使用する。