イベントの発生順序とは?

JavaScriptでイベントが発生したとき、ブラウザは特定の順序でイベントを処理します。この順序を理解することは、複雑なイベント処理を行う際に非常に重要です。
特に、親要素と子要素の両方にイベントリスナーを設定している場合、どちらが先に実行されるのかを知っておく必要があります。その仕組みに関係するのが、キャプチャリング、ターゲット、バブリングという3つのフェーズです。
キャプチャリングとバブリングの復習
イベント伝播には、主に次のフェーズがあります。
- キャプチャリングフェーズ:外側の要素からターゲット要素に向かってイベントが伝わる
- ターゲットフェーズ:実際にイベントが発生した要素で処理される
- バブリングフェーズ:ターゲット要素から外側の要素に向かってイベントが伝わる
通常、addEventListener()で登録したイベントリスナーは、バブリングフェーズで実行されます。
イベントの発生順序の基本的な例
イベントがどの順序で発生するかを、次のコード例で確認します。
const div = document.querySelector("div");
const button = document.querySelector("button");
// キャプチャリングフェーズで実行される
div.addEventListener("click", () => {
console.log("div: キャプチャリングフェーズ");
}, true);
// バブリングフェーズで実行される
div.addEventListener("click", () => {
console.log("div: バブリングフェーズ");
}, false);
// ターゲット要素で実行される
button.addEventListener("click", () => {
console.log("button: ターゲットフェーズ");
}, false);
このコードでは、divとbuttonにクリックイベントを設定しています。buttonをクリックすると、イベントは次の順序で処理されます。
- キャプチャリングフェーズ:外側の
div要素で処理される - ターゲットフェーズ:クリックされた
button要素で処理される - バブリングフェーズ:内側から外側へ戻り、
div要素で処理される
キャプチャリングフェーズとバブリングフェーズの違い
| フェーズ | 動作 |
|---|---|
| キャプチャリングフェーズ | 外側の要素から内側のターゲット要素へ向かってイベントが伝わる |
| ターゲットフェーズ | 実際にクリックされた要素など、イベントの発生元で処理される |
| バブリングフェーズ | ターゲット要素から外側の要素へ向かってイベントが伝わる |
キャプチャリングの利用
キャプチャリングフェーズは、イベントが発生した要素よりも外側の要素で先に処理を行いたい場合に利用します。
addEventListener()の第3引数にtrueを指定すると、キャプチャリングフェーズでイベントを処理できます。
キャプチャリングを有効にした例
const div = document.querySelector("div");
const button = document.querySelector("button");
div.addEventListener("click", (event) => {
console.log("div: キャプチャリングフェーズ");
}, true);
button.addEventListener("click", (event) => {
console.log("button: クリックされました");
}, false);
この例では、buttonをクリックしても、先に外側のdivのキャプチャリングリスナーが実行されます。
ただし、通常のイベント処理ではバブリングフェーズを使うことが多く、キャプチャリングは必要な場合だけ使うのが一般的です。
イベントフローの制御
イベントの伝播を途中で止めたい場合は、stopPropagation()を使用します。
また、同じ要素に複数のイベントリスナーが設定されている場合に、後続のリスナーも含めて止めたい場合はstopImmediatePropagation()を使用します。
イベント伝播を防ぐ例
const div = document.querySelector("div");
const button = document.querySelector("button");
// buttonのクリックでイベントの伝播を停止
button.addEventListener("click", (event) => {
console.log("button: クリックされました");
event.stopPropagation();
});
// divのクリックリスナー
div.addEventListener("click", () => {
console.log("div: クリックされました");
});
この例では、buttonをクリックしてもevent.stopPropagation()によってイベント伝播が止まるため、外側のdivのクリック処理は実行されません。
stopPropagationとstopImmediatePropagationの違い
| メソッド | 説明 |
|---|---|
stopPropagation() |
イベントが親要素や外側の要素へ伝わるのを止めます。 |
stopImmediatePropagation() |
イベント伝播を止めるだけでなく、同じ要素に登録された後続のイベントリスナーも実行しません。 |
基本的にはstopPropagation()で十分なことが多く、stopImmediatePropagation()は特別な理由がある場合に使います。
キャプチャリングとバブリングの使い分け
キャプチャリングとバブリングは、どちらのフェーズでイベントを処理するかによって使い分けます。
通常は、デフォルトであるバブリングフェーズを使えば問題ありません。特定の状況で外側の要素を先に処理したい場合に、キャプチャリングを検討します。
キャプチャリングとバブリングの違いのまとめ
| フェーズ | 動作の順序 | 主な使用目的 |
|---|---|---|
| キャプチャリング | 外側から内側へイベントが伝わる | 外側の要素で先に処理したい場合 |
| バブリング | 内側から外側へイベントが伝わる | 通常のクリック処理やイベント委任 |
イベント委任との関係
イベントのバブリングを利用した代表的な手法に、イベント委任があります。
イベント委任とは、複数の子要素それぞれにイベントリスナーを設定するのではなく、親要素にまとめてイベントリスナーを設定する方法です。
const list = document.querySelector("#list");
list.addEventListener("click", (event) => {
if (event.target.matches("li")) {
console.log("クリックされた項目:", event.target.textContent);
}
});
この例では、li要素を直接監視するのではなく、親要素である#listにイベントリスナーを設定しています。
クリックイベントがバブリングによって親要素へ伝わるため、どのliがクリックされたかをevent.targetで判定できます。
初心者が覚えておきたいポイント
- イベントは「キャプチャリング → ターゲット → バブリング」の順に処理されます。
- 通常はバブリングフェーズでイベント処理を行います。
addEventListener()の第3引数にtrueを指定すると、キャプチャリングフェーズで処理されます。stopPropagation()を使うと、イベントが外側の要素へ伝わるのを止められます。- イベント委任では、バブリングを利用して親要素で子要素のイベントをまとめて処理できます。
まとめ
- JavaScriptのイベントには発生順序がある。
- イベントはキャプチャリング、ターゲット、バブリングの順で処理される。
- 通常のイベントリスナーはバブリングフェーズで実行される。
- キャプチャリングを使うと、外側の要素で先にイベントを処理できる。
stopPropagation()を使うと、イベント伝播を途中で止められる。- バブリングを利用すると、イベント委任によって効率的なイベント管理ができる。