stopPropagation とは何か
stopPropagation は「イベントが親要素へ伝わっていく流れ(伝播)を止める」ためのメソッドです。クリックなどのイベントは、最初に操作された要素から外側へ向かって段階的に届きます(バブリング)。ここが重要です:親でイベント委譲(親に1つだけリスナーを付けて子の操作に反応)している設計では、安易な stopPropagation が“親の正しい処理”を遮ってバグになります。必要な場面だけ、理由を明確にして使いましょう。
伝播の仕組み(キャプチャ → ターゲット → バブリング)
段階のイメージと出力の違い
<div id="outer" style="padding:10px;border:1px solid #ccc">
<button id="inner">押す</button>
</div>
<script>
outer.addEventListener("click", () => console.log("キャプチャ:外"), { capture: true });
inner.addEventListener("click", () => console.log("ターゲット:内"));
outer.addEventListener("click", () => console.log("バブリング:外"));
</script>
HTMLイベントは「キャプチャ(外側から内側へ探す段階)→ターゲット(実際に押された要素)→バブリング(内側から外側へ戻る段階)」の順に処理されます。ここが重要です:stopPropagation は“今の段階より外へ行かせない”。バブリング中に止めれば親へ届きません。キャプチャ中に止めれば、内側に降りずに遮断できます。
基本の使い方(親の処理を止める)
内側だけで完結させ、親へ伝えない
<div id="card" style="padding:10px;border:1px solid #ccc">
<button id="delete">削除</button>
</div>
<script>
card.addEventListener("click", () => console.log("カードクリック"));
delete.addEventListener("click", (e) => {
e.stopPropagation(); // 親へ伝えない
console.log("削除ボタンの専用動作");
});
</script>
HTMLここが重要です:delete のクリックで card のクリック処理が走ると困る設計なら、内側で stopPropagation。対策前提が“親の処理を走らせないこと”にある場合に限定します。
preventDefault との違い(役割を絶対に混同しない)
既定動作と伝播は別もの
<a id="link" href="/next">次へ</a>
<script>
link.addEventListener("click", (e) => {
// e.preventDefault(); // ページ遷移を止める(既定動作)
// e.stopPropagation(); // 親へイベントを伝えない(伝播)
});
</script>
HTMLpreventDefault は「ブラウザの既定動作(遷移・送信・スクロール等)」を止めます。stopPropagation は「親へイベントが届くのを止めます」。ここが重要です:遷移を止めたいのに stopPropagation を使うのは誤用。役割を明確に分けましょう。
stopImmediatePropagation(同じ要素内の他ハンドラも止める)
同一要素に複数のリスナーがある場合の完全停止
<button id="btn">押す</button>
<script>
btn.addEventListener("click", () => console.log("A"));
btn.addEventListener("click", (e) => { e.stopImmediatePropagation(); console.log("B(ここで完全停止)"); });
btn.addEventListener("click", () => console.log("C"));
</script>
HTMLここが重要です:stopPropagation は“親への伝播”だけ止めます。stopImmediatePropagation は“同じ要素の後続ハンドラ”も含めて完全停止。強力なので乱用は避け、明確な理由があるときだけ使います。
イベント委譲との付き合い方(極力、条件分岐で解決する)
親一箇所で子の操作を選別する
<ul id="list"></ul>
<script>
list.addEventListener("click", (e) => {
const del = e.target.closest(".delete");
if (del) { del.closest("li")?.remove(); return; }
const edit = e.target.closest(".edit");
if (edit) { console.log("編集"); return; }
// 他のクリックは無視
});
</script>
HTMLここが重要です:委譲設計では“親で選別”すれば stopPropagation が不要です。closest と条件分岐で意図した対象だけ処理し、他は何もしない。これが規模に強く、保守性も高い基本パターンです。
よくある使用場面(外クリック検知・ネストしたUI・モーダル)
メニューの外クリックで閉じる(stopPropagationでメニュー内クリックを除外)
<div id="menu" class="open">
<button id="toggle">開閉</button>
</div>
<script>
toggle.addEventListener("click", (e) => { e.stopPropagation(); /* メニュー内クリックは外へ伝えない */ });
document.addEventListener("click", () => {
menu.classList.remove("open"); // 外側クリックで閉じる
});
</script>
HTMLここが重要です:内側で stopPropagation を使い、外側(document)で“外クリック”を受ける。外クリック検知の定番パターンです。
ネストしたカードの二重反応を防ぐ
<div id="card" style="padding:10px;border:1px solid #ccc">
<div id="chip" style="display:inline-block;padding:4px;border:1px solid #aaa">タグ</div>
</div>
<script>
card.addEventListener("click", () => console.log("カード選択"));
chip.addEventListener("click", (e) => { e.stopPropagation(); console.log("タグ編集"); });
</script>
HTMLここが重要です:同じクリックで“カード選択”と“タグ編集”が重なるのを避ける。UIの責務が衝突する箇所でのみ使います。
モーダルの中は操作可、背景クリックで閉じる
<div id="backdrop" style="position:fixed;inset:0;background:rgba(0,0,0,.4)">
<div id="modal" style="width:280px;margin:80px auto;background:white;padding:12px">内容</div>
</div>
<script>
modal.addEventListener("click", (e) => e.stopPropagation()); // モーダル内のクリックは外へ伝えない
backdrop.addEventListener("click", () => backdrop.remove()); // 背景クリックで閉じる
</script>
HTMLここが重要です:背景と内容で“役割が違う”ため、内側は遮断、外側で閉じる。直感に沿った設計になります。
落とし穴と回避策(乱用しない・デバッグしやすく)
委譲の前提を壊す乱用
stopPropagation をあちこちに入れると、親の処理が予期せず止まり、拡張や修正が難しくなります。ここが重要です:まず委譲で“親が選別”する設計にし、それでも衝突する箇所だけに限定して使う。
preventDefault と併用の混乱
既定動作を止めたいのに stopPropagation を使ってしまうミスに注意。ここが重要です:遷移・送信・スクロールを止めるのは preventDefault。伝播を止めるのは stopPropagation。両者は別の目的。
同一要素の複数ハンドラが残る
部分的に止めても別のハンドラが動いてしまう場合は、stopImmediatePropagation を検討。ただし強すぎるため、構造的な整理(ハンドラを一つに集約)を優先します。
シャドウDOMや複雑なツリー
想定外の target になることがあります。ここが重要です:e.composedPath() で実際の通過経路を確認し、closest と contains で“意図した塊”を基準に判定するのが堅牢です。
実践例(外クリック検知の完全版、キーボード混在)
外クリック検知(contains で範囲判定)
<div id="menu" class="open">…</div>
<script>
document.addEventListener("click", (e) => {
if (!menu.classList.contains("open")) return;
if (!menu.contains(e.target)) menu.classList.remove("open");
});
// メニュー内クリックは外へ伝えない
menu.addEventListener("click", (e) => e.stopPropagation());
</script>
HTMLここが重要です:stopPropagation と contains 判定を組み合わせると誤判定が減ります。先に“範囲チェック”できるなら stopPropagation なしでも設計可能です。
キーボードとクリックの両対応(アクセシビリティ)
<button id="open">開く</button>
<div id="dialog" hidden role="dialog" aria-modal="true">…</div>
<script>
open.addEventListener("click", () => dialog.hidden = false);
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && !dialog.hidden) dialog.hidden = true;
});
dialog.addEventListener("click", (e) => e.stopPropagation()); // 内側操作は外へ伝えない
document.addEventListener("click", () => { if (!dialog.hidden) dialog.hidden = true; });
</script>
HTMLここが重要です:クリックの伝播制御に加えて、Esc の既定動作を設計に取り込み、誰でも直感的に閉じられる UI にします。
まとめ
stopPropagation は「イベントの伝播」を止めるためのメソッドです。preventDefault(既定動作停止)とは目的が違うことを徹底理解し、委譲で“親が選別”する設計を基本に、衝突する箇所だけ最小範囲で使う。強力版の stopImmediatePropagation は乱用せず、必要時のみ。外クリック検知やモーダル・ネストUIで“内側は遮断、外側で反応”を実現するのが典型。伝播の段階(キャプチャ/ターゲット/バブリング)を意識し、closest・contains・composedPath を活用すれば、初心者でも読みやすく壊れにくいイベント制御が書けます。

