preventDefault()とstopPropagation()は既定動作を止めるかイベント伝播を止めるかで使い分ける
公開日: 執筆:佐藤
preventDefault()は、リンクをたどる、チェックボックスの状態を変えるなど、イベントに対応する既定動作を止めます。stopPropagation()は、イベントがDOMツリーの別の要素へ伝わるのを止めます。一方を呼んでも、もう一方の働きは止まりません。
クリック後に何が起きたかを調べるときは、ブラウザーの既定動作と、登録したEvent Listenerの呼出しを分けます。画面遷移を止めたいならpreventDefault()、親要素のListenerまで動かしたくないならstopPropagation()を選びます。
二つのメソッドは止める対象が違う
| メソッド | 止める対象 | 止めない対象 |
|---|---|---|
preventDefault() |
キャンセル可能なイベントの既定動作 | capture、target、bubbleによるイベント伝播 |
stopPropagation() |
現在の要素から先のイベント伝播 | リンクやフォーム部品などの既定動作 |
stopImmediatePropagation() |
伝播と、現在の要素に残る後続Listener | 既定動作 |
preventDefault()が働くのは、event.cancelableがtrueのイベントです。キャンセルできないイベントで呼んでも効果はありません。既定動作が止まったかは、event.defaultPreventedで確認できます。
preventDefault()は、イベントを消すメソッドではありません。Listenerの処理は続き、イベントも伝播します。反対に、stopPropagation()を呼んでも、ブラウザーが実行する既定動作は残ります。
イベントはcapture、target、bubbleの順に進む
DOMのイベントは、対象要素まで祖先を下るcapture、対象要素で処理するtarget、祖先を逆向きに上るbubbleの順に進みます。イベントごとにbubbleの有無は異なり、event.bubblesがfalseならbubbleはありません。
addEventListener()の第三引数をtrueにすると、Listenerはcaptureで呼ばれます。falseまたは省略時は、targetとbubbleで呼ばれます。stopPropagation()は、呼ばれた位置から先の要素へイベントが進むことを止めます。
ただし、同じ要素に複数のListenerが登録されている場合、stopPropagation()を呼んだListenerより後のListenerは実行されます。同じ要素に残るListenerも止める必要がある場合は、stopImmediatePropagation()を使います。
リンクを含むカードでは既定動作と親の処理を分ける
次は説明用の仮例です。カード全体と、その中の詳細リンクに、それぞれclickのListenerがあるとします。
<div id="product-card">
<a id="details-link" href="/details/">詳細を見る</a>
</div>
var card = document.getElementById('product-card');
var link = document.getElementById('details-link');
card.addEventListener('click', function () {
// カード全体のクリック処理
}, false);
link.addEventListener('click', function (event) {
event.preventDefault();
// リンク先へ移動せず、別の詳細表示を行う
}, false);
この例でpreventDefault()だけを呼ぶと、リンク先への移動は止まります。clickイベントはbubbleするため、カードに登録したListenerは実行されます。
リンクの処理だけを行い、カードのListenerを動かさない設計なら、二つを明示します。
link.addEventListener('click', function (event) {
event.preventDefault();
event.stopPropagation();
// リンク先へ移動せず、別の詳細表示を行う
}, false);
両方を常に書くのではなく、止める理由が二つあるときだけ併用します。親要素のListenerも動く設計なら、stopPropagation()は不要です。
return falseを二つのメソッドの代わりにしない
Listenerからfalseを返したときの扱いは、Listenerの登録方法や利用するライブラリによって異なります。DOMのaddEventListener()では、戻り値をpreventDefault()とstopPropagation()の一括指定として使いません。
止める対象は、メソッド名で明示します。既定動作だけならpreventDefault()、伝播だけならstopPropagation()、両方なら両方を呼びます。コードを読んだ人も、画面遷移と親要素の処理のどちらを止めたのか判断できます。
cancelableとbubblesを確認してから直す
イベント処理が意図どおりにならないときは、次の順に確認します。
event.cancelableを見て、既定動作を止められるイベントか確認するevent.defaultPreventedを見て、すでに既定動作が止められたか確認するevent.targetとevent.currentTargetを見て、発生した要素とListenerを登録した要素を分けるevent.bubblesとevent.eventPhaseを見て、どの伝播段階で処理しているか確認する- 同じ要素の後続Listenerまで止める必要があるかを判断する
最初の一手は、問題が起きるListenerからreturn falseを外し、止めたい対象を一つ書き出すことです。ブラウザーの既定動作ならpreventDefault()、別要素への伝播ならstopPropagation()へ置き換えます。
関連記事

