概要
イベント伝播とは「イベントが上に伝わる仕組み」ではない。
それは**“UI階層を設計し、意図したハンドリングポイントにのみイベントを到達させる構造的制御”**である。
DOMイベントには キャプチャフェーズ(上から下へ) と バブリングフェーズ(下から上へ) が存在する。
この2つの流れを 設計としてコントロール できなければ、
意図しないイベント処理 / グローバル誤反応 / UI整合性崩壊 を引き起こす。
1. イベント伝播の流れ(基本構造)
| キャプチャフェーズ | ↓ |
| ターゲットフェーズ | • |
| バブリングフェーズ | ↑ |
- ✅ addEventListener の第3引数 / options.capture: true でキャプチャ対応
- ✅ 通常はバブリング(下 → 上)でハンドラが発火
2. キャプチャ・バブリングの使い分け
// キャプチャフェーズ
element.addEventListener('click', handler, { capture: true });
// バブリング(デフォルト)
element.addEventListener('click', handler);
- ✅ グローバルな UI 制御(例: モーダル閉じ) → キャプチャで先手制御
- ✅ ローカルな操作(例: ボタン押下) → バブリングで処理
3. stopPropagation / stopImmediatePropagation の意味と用途
element.addEventListener('click', (e) => {
e.stopPropagation(); // 親への伝播を阻止
});
element.addEventListener('click', (e) => {
e.stopImmediatePropagation(); // 同一要素上の他ハンドラも停止
});
- ✅ 明示的に伝播を止めることで 意図した箇所だけにイベントを集中
- ❌ 無差別に stop すると他の処理を破壊する → 局所用途で慎重に
4. UI階層設計におけるイベント伝播戦略
- グローバル系: ドキュメントやルート → キャプチャで抑止
- ローカル系: コンポーネント内部 → バブリングで分岐制御
- モーダル / ドロップダウン: 「外をクリックしたら閉じる」の設計
document.addEventListener('click', (e) => {
if (!modal.contains(e.target)) closeModal();
}, true); // キャプチャフェーズ
- ✅ UI閉じロジックや解除判定はキャプチャが強力
- ✅ 「中で押したら閉じない」 →
stopPropagation
と組み合わせ
5. コンポーネント設計におけるイベント伝播設計
// Button.vue
<button @click.stop="submit">送信</button>
// Modal.vue
<div @click.self="close"> <!-- 外側クリックのみ反応 -->
<div class="modal-inner">...</div>
</div>
- ✅ Vue/Reactでは
.stop
,.self
,.capture
等の 構造的イベント修飾が推奨 - ✅ イベント構造はUIコンポーネントの責務として扱う
6. イベントバブリングとパフォーマンス設計
document.addEventListener('click', handleAll); // NG: 無差別処理
// OK: イベントデリゲーション + セレクタフィルタ
container.addEventListener('click', (e) => {
if (e.target.matches('.item')) handleItemClick(e.target);
});
- ✅ イベントはなるべく 親1点で受けるデリゲーション方式を採用
- ✅ セレクタベースで対象を限定し、性能と構造を両立
設計判断フロー
① このイベントは「階層内」か「階層を超える」か? → バブリング / キャプチャ設計
② stopPropagation は本当に必要か? → 他UIへの影響を検討
③ イベント対象が複数あるか? → デリゲーションで構造化
④ UIが「クリックされるべき範囲」を持っているか? → .self や contains() の活用
⑤ イベントハンドラの設置位置が、構造的に正しいか?
よくあるミスと対策
❌ モーダル内クリックで外部イベントが発火して閉じてしまう
→ ✅ .stopPropagation()
または .self
+ キャプチャ設計で制御
❌ 全体イベントでクリックが過剰検知される
→ ✅ セレクタフィルタ or contains() で正確な条件分岐
❌ イベントが動作する理由がコード上で分からない
→ ✅ キャプチャ or バブリングか明示し、コメントで意図を記述
結語
イベント伝播とは「ブラウザの仕様」ではない。
それは**“UI階層を正確に意識し、構造的に制御し、意図した反応だけを保証する設計の仕組み”**である。
- キャプチャとバブリングを明確に使い分け
- stopPropagation は責任を持って限定的に使用し
- UIの構造と意図に沿ってイベント制御を設計する
JavaScriptにおけるイベント伝播設計とは、
“意図を持った反応だけを許す、階層的インタラクションの設計戦略”である。