• preventDefault()とstopPropagation()は既定動作を止めるかイベント伝播を止めるかで使い分ける

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を確認してから直す

イベント処理が意図どおりにならないときは、次の順に確認します。

  1. event.cancelableを見て、既定動作を止められるイベントか確認する
  2. event.defaultPreventedを見て、すでに既定動作が止められたか確認する
  3. event.targetとevent.currentTargetを見て、発生した要素とListenerを登録した要素を分ける
  4. event.bubblesとevent.eventPhaseを見て、どの伝播段階で処理しているか確認する
  5. 同じ要素の後続Listenerまで止める必要があるかを判断する

最初の一手は、問題が起きるListenerからreturn falseを外し、止めたい対象を一つ書き出すことです。ブラウザーの既定動作ならpreventDefault()、別要素への伝播ならstopPropagation()へ置き換えます。

戻る