JavaScriptのバブリングとは?親要素への伝播と止め方を徹底解説
Web制作やフロントエンド開発の現場において、「子要素のボタンをクリックしただけなのに、なぜか親要素の枠膜や背景のクリックイベントまで同時に発火してしまった」というトラブルに直面した経験を持つ開発者は少なくありません。この現象の背景にあるのが、ブラウザの標準仕様であるイベントバブリング(Event Bubbling)です。
DOMイベントの仕組みを正しく把握していないと、モーダルウィンドウの誤動作や意図しない画面遷移といった深刻なUIバグを引き起こす原因になります。2026年現在のモダンフロントエンド環境においても、ReactやVue、あるいはプレーンなVanilla JSを扱う上で、イベント伝播のライフサイクルと適切な制御手法を理解しておくことは必須のリテラシーといえます。
📌 【この記事の重要ポイントまとめ】
- 要点1:バブリングとは発生したDOMイベントが親要素へと泡のように遡って伝播するブラウザの標準仕様です。
- 要点2:意図しない挙動は
stopPropagation()で防げますが、preventDefault()との混同や乱用には注意が必要です。- 要点3:仕組みを逆手に取った「イベント委譲」を活用することで、メモリ消費を大幅に削減し高速なUI設計が実現できます。
【基本解説】JavaScriptのバブリングとは?親要素へ伝播する仕組み
JavaScriptバブリング(イベントバブリング)とは、HTMLドキュメント内でクリックやキー入力などのDOMイベントが発生した際、そのイベントがターゲットとなった要素から親要素、祖先要素へと順に伝播していく挙動を指します。水の中で発生した泡(バブル)が水面に向かってポコポコと浮上していく様子に似ていることから、この名称が付けられました。
W3CおよびWHATWGが策定するDOMイベント仕様に基づき、ブラウザ上のほぼすべてのUIイベントはツリー構造の上位に向かって自動的に通知されます。たとえば、以下のような入れ子構造を考えてみましょう。
<div class="card"> <button class="btn">削除</button> </div>
ここでbutton要素をクリックすると、ブラウザはまずbutton自身のクリック処理を実行します。しかし処理はそこで終わりません。次に親であるdiv.card、さらにその親であるbody、html、最終的にはwindowオブジェクトに至るまで、同一のクリックイベントが次々と通知されていきます。もし親のdiv.cardにもクリック時のイベントリスナーが設定されていた場合、意図せず親側の処理まで実行されてしまうのが「誤作動」の正体です。

イベント伝播の全貌|キャプチャリング・ターゲット・バブリングの3段階
ブラウザ内部で発生するイベント伝播(Event Propagation)は、単に下から上に登るだけではありません。W3C標準仕様では、イベント処理は厳密に3つのフェーズを経て実行されます。
第1段階がキャプチャリング(Capturing Phase)です。イベントが発生すると、ブラウザの最上位であるwindowからターゲット要素を目指してツリーを駆け下りていきます。第2段階がターゲットフェーズ(Target Phase)で、実際に操作された最深部の要素に到達した瞬間を指します。そして第3段階として、再び親要素へと駆け上がっていくバブリングフェーズ(Bubbling Phase)が実行されます。
通常のaddEventListener('click', handler)で登録したリスナーは、デフォルトで第3段階のバブリングフェーズで発火するように設計されています。第1段階のキャプチャリングフェーズでイベントを捕捉したい場合は、第3引数に{ capture: true }またはtrueを明示的に渡す必要があります。
| フェーズ・機能名 | 伝播の方向と役割 | 一般的な実務での利用頻度 | 開発時の注意点・評価 |
|---|---|---|---|
| キャプチャリング | windowからDOMツリーを下って対象要素へ到達する | 極めて稀(全体の5%未満) | 特殊なトラッキングやグローバルな入力割り込み時のみ使用 |
| バブリング | 対象要素から親・祖先要素、windowへと遡る | 標準(全体の95%以上) | 親要素への連鎖発火リスクがあるが、後述のイベント委譲に必須 |
| stopPropagation | 現在位置でイベントの伝播(親方向への移動)を打ち切る | ピンポイントで利用 | 不要な連鎖を防ぐ強力な手段だが、乱用すると上位解析タグが壊れる |
| preventDefault | ブラウザ既定の動作(リンク遷移やフォーム送信)を無効化 | フォーム制御等で頻出 | イベント伝播自体は止まらないため、バブリング対策にはならない |
【実態検証】現場エンジニアが直面するバブリング誤作動とtargetの罠
開発コミュニティ(GitHubのIssue、Stack Overflow、国内のZennやQiitaなど)で報告されるUIのバグ相談を精査すると、バブリングに起因する事故の大半は「モーダルダイアログの実装」と「リスト内のアクションボタン」に集中しています。
典型的な事例が「モーダルの背景をクリックしたら閉じる」という機能を実装したケースです。モーダルの背景要素(オーバーレイ)に「閉じる」イベントを設定し、その内側に配置した白背景のコンテンツエリアや入力フォームをクリックした際、バブリングによって親の背景要素までクリックが伝わってしまい、「フォームを入力しようとした瞬間にモーダル全体が勝手に閉じてしまう」という不具合が頻発します。
このトラブルを正しく解決・デバッグする鍵となるのが、イベントオブジェクトが持つtargetとcurrentTargetの違いを正確に見極めることです。
event.targetは「ユーザーが実際にクリックした最深部の要素」を指し、event.currentTargetは「現在そのイベントリスナーが登録されている要素自身」を指します。モーダルの背景要素リスナー内でif (event.target !== event.currentTarget) return;と条件分岐を入れるだけで、子要素からのバブリングによる誤動作をエレガントに弾くことができます。

一般に知られていない盲点とネットの誤解|バブリング止め方の真実
初学者のコードレビューやWeb上の古い技術ブログで頻繁に見かけるのが、preventDefaultとの違いを混同した誤った実装です。「クリック時の親への伝播を止めたい」という場面でevent.preventDefault()を記述しても、バブリングは一切止まりません。
preventDefault()の役割は、あくまで<a>タグをクリックしたときのURL遷移や、<form>送信時の画面リロードといった「ブラウザが持つ標準の振る舞い」をキャンセルすることに限定されます。親要素へのイベント通知を物理的に遮断するバブリング止め方としては、event.stopPropagation()を呼び出す必要があります。
しかし、ここで現場のシニアエンジニアが警鐘を鳴らすのが「とりあえずstopPropagation()を書くアンチパターン」です。上位要素への伝播を完全に遮断してしまうと、以下のような二次災害を引き起こすリスクが生じます。
第一に、Google Tag Manager(GTM)や各種アクセス解析ツールが提供する「ページ全体のクリック計測スクリプト」が、該当ボタンのクリックを検知できなくなります。第二に、画面のどこかをクリックしたときにメニューを閉じるグローバルな「外側クリック検知(Click Outside)」のロジックが破壊されます。安易な伝播停止は、プロジェクト全体のアーキテクチャを破綻させる危険性を孕んでいるのです。
【プロの結論】イベント委譲を使いこなすアーキテクチャ設計と判断基準
バブリングは単なる「防ぐべきトラブルの原因」ではありません。ブラウザのこの伝播特性を意図的に活用した設計パターンがイベント委譲(Event Delegation)です。
たとえば、1,000件のアイテムが並ぶリストUIを構築する場合、1,000個の<li>要素それぞれに個別のリスナーを登録すると、ブラウザのメモリを圧迫し初期描画パフォーマンスを著しく低下させます。しかし、バブリングの性質を利用して親の<ul>要素1箇所にだけリスナーを登録しておけば、子要素のクリックイベントが自然と親に集約されるため、リスナー登録数を「1個」で済ませることが可能です。動的に子要素が追加・削除されても再登録の手間が発生しません。
イベント委譲・バブリング制御の採用判断基準
【イベント委譲を採用すべきケース】
・リスト項目やテーブル行が数十件以上あり、動的な追加・削除が頻繁に行われるUI
・SPA(Single Page Application)等でメモリ消費を最小限に抑えたい長大なフィード画面
【stopPropagationによる停止が推奨されるケース】
・入れ子になったカードUIの中で、「記事詳細へのリンク」と「お気に入りボタン」が独立して動作すべき箇所
・モーダル内部のクリックが背景オーバーレイの閉じる処理に干渉するのを防ぐ局所的な処理

【バブリング と は】に関するよくある質問(FAQ)
Q1:すべてのDOMイベントがバブリングするのですか?
A1:いいえ、すべてのイベントがバブリングするわけではありません。代表例として、フォーム要素のfocusやblur、画像やリソースのloadやunload、マウスのmouseenterやmouseleaveなどはバブリングしません。これらを親要素で監視したい場合は、バブリング版であるfocusinやfocusout、mouseoverを使用するか、キャプチャリングフェーズを利用します。
Q2:stopPropagation() と stopImmediatePropagation() の違いは何ですか?
A2:stopPropagation()は「親要素への伝播」のみを止めます。一方、同一のHTML要素に対して複数のイベントリスナーが登録されている場合、stopImmediatePropagation()を使うと親への伝播だけでなく、その要素に登録された以降のリスナーの実行もすべて即座に停止させます。
Q3:ReactやVueなどのモダンフレームワークでもバブリングは同じように起きますか?
A3:全く同様に発生します。ReactではSyntheticEvent(合成イベント)システムを通じてDOMツリーに沿ったバブリングをエミュレートしており、コンポーネントを跨いで親のonClickが発火します。子要素のハンドラー内でe.stopPropagation()を呼ぶことで、プレーンなJavaScriptと同様に親への伝播を止めることが可能です。
まとめ:今後の動向と失敗しないための判断基準
JavaScriptのイベントバブリングは、一見すると予期せぬ不具合を引き起こす厄介な挙動に思えるかもしれません。しかしその本質は、階層構造を持つDOMツリーにおいて、効率的なイベント駆動アーキテクチャを実現するために設計された不可欠なメカニズムです。
原因不明のイベント多重発火に直面した際は、闇雲にstopPropagation()でフタをするのではなく、キャプチャリングからバブリングに至るイベントフローを正しくトレースすることが重要です。event.targetとevent.currentTargetの識別、そしてpreventDefault()との機能差を明確に切り分けることで、堅牢でメンテナンス性の高いWebアプリケーションを構築することができます。 (出典: バブリング と は(Yahoo!ニュース))