TL;DR

  • DOM のイベントは キャプチャ → ターゲット → バブリング の3フェーズを必ず通る
  • addEventListener で指定するのは 「どのフェーズで handler を呼ぶか」
  • デフォルトは バブリングフェーズ

はじめに

JavaScriptを使っていると、

  • なぜ親のイベントが反応するのか
  • キャプチャとバブリングはどう使い分けるのか

といった疑問に一度はぶつかります。

この記事では、DOM イベント伝搬の仕組みを理解することを目標にします。


イベント伝搬の3フェーズ

DOM のイベントは、必ず次の3フェーズを通ります

  1. キャプチャフェーズ
  2. ターゲットフェーズ
  3. バブリングフェーズ

これは仕様で決まっており、Reactなどのフレームワークには依存しません。

伝搬の流れ

たとえば、以下のような DOM 構造で <button> をクリックした場合:

<div id="outer">
  <div id="inner">
    <button id="target">クリック</button>
  </div>
</div>

イベントは次のように伝搬します:

📍 1. キャプチャフェーズ(上から下へ)

   document

   #outer

   #inner

📍 2. ターゲットフェーズ

   #target ← クリックされた要素

📍 3. バブリングフェーズ(下から上へ)

   #inner

   #outer

   document

この一連の流れの中で、各要素に登録されたイベントハンドラが呼ばれます。


キャプチャフェーズ(Capturing phase)

由来
capture = 捕まえる

挙動

  • document → 親 → 子 へ降りていきます
  • 「これから起きるイベント」を先回りして処理します

設定方法

element.addEventListener('click', handler, { capture: true });

ターゲットフェーズ(Target phase)

  • 実際にイベントが発生した要素
  • 専用の登録方法はありません
  • ターゲット要素に登録された handler が実行される瞬間を指します
target.addEventListener('click', handler);

バブリングフェーズ(Bubbling phase)

由来
bubble = 泡
→ 下から上に浮かび上がるイメージ

挙動

  • 子 → 親 → document へ伝わります
  • 「起きた結果」に親が反応します

設定方法(デフォルト)

element.addEventListener('click', handler);
// または
element.addEventListener('click', handler, { capture: false });

適している用途の違い

キャプチャが適している用途(例外)

  • モーダルの外側クリック検知
  • グローバルなキーボードショートカット
  • 操作ロック・ガード処理
  • アナリティクス・監査ログ

バブリングが適している用途(デフォルト)

  • 通常の UI 操作
  • ボタン・リンクのクリック
  • イベント委譲

event.target と event.currentTarget の違い

プロパティ意味
event.target実際にイベントが発生した要素
event.currentTarget今 handler が実行されている要素

list.addEventListener('click', (e) => {
  console.log(e.target);        // li
  console.log(e.currentTarget); // ul
});
  • target発生源
  • currentTarget責務の主体

フェーズが進むにつれて currentTarget は変わりますが、 target は常に同じです。


おわりに

これまで雰囲気でaddEventListenerなどを使っていましたが、イベント伝搬の仕組みを理解することができました。


参考リンク