[JavaScript] スクロールイベントにPassive Event Listener指定してパフォーマンスを向上させる方法
DOM の新仕様として、スクロールのパフォーマンスを改善するためにaddEventListener
に Passive Event Listeners というオプションが追加された。
Scroll Jank
ページのスクロール時に発生する(スクロールが詰まったような)遅延を「Scroll Jank」と呼ぶ。 こういった Scroll Jank は、スクロールやタッチイベントリスナーに原因がある。
イベント内でpreventDefault()
を実行した場合、デフォルトのイベントはキャンセルされる。
現在ブラウザは、イベント内でpreventDefault()
が実行されるか否かは、そのイベントが実行が終了するまで判定ができないため、イベント内の処理が終了するの待つことになる。
スクロールイベントもpreventDefault()
が実行された場合は、スクロールはキャンセルされるが、同様にイベント内でpreventDefault()
が実行されるか否かを判定できるまでスクロールが止まることになる(遅延が発生する)。
これが Scroll Jank が発生する主な原因である。
EventListenerOptions passive
とは
処理実行前に「preventDefault()
を実行していない」ことが判定できれば、Scroll Jank の問題は解決できる。こういった中でaddEventListener
に Passive Event Listeners というオプションが追加された。
追加されたオプションは、addEventListener
の第三引数にoptions
({passive: true}
)を指定する事で「処理がpreventDefault()
を実行していない」という事が明示できるようになった。
これより、スクロールイベントのリスナーにこのオプションを指定することで処理終了後ではなく、スクロールをすることができるようになった。
document.addEventListener("touchmove", func, { passive: true });
options > passive:
listener
がpreventDefault()
を呼び出さないことを表す Boolean 値です。true
が指定された状態でlistener
がpreventDefault()
を呼び出すと、ユーザーエージェントはその呼び出しを無視し、コンソールに警告を出力します。
wheel
、mousewheel
、touchstart
、touchmove
も passive
指定をすると良い。
非対応ブラウザとの互換の問題
元々addEventListener
の第三引数には、useCapture
が定義されていた。useCapture
の説明は割愛するが、今後useCapture
を指定する場合は、{capture: true}
といった形で指定する。
モダンブラウザの殆どが Passive event listener に対応しているが、Can I use… Support tables for HTML5, CSS3, etcを見たら分かるように Internet Explorer 11 だけが未対応となっている。
もし、こういった非対応ブラウザの第三引数にoptions
の Object を渡してしまうと、useCapture
がtrue
評価になってしまう。
非対応ブラウザでuseCapture
が意図しない指定になるのはよろしくはないので、回避したい場合は Passive event listener に対応しているのか判定が必要になる。
非対応ブラウザ向けに判定処理を実装する事が出来る。EventTarget.addEventListener() - Web API インターフェイス | MDNには、こういった判定処理が紹介されている。
/* "passive" が使えるかどうかを検出 */
var passiveSupported = false;
try {
window.addEventListener(
"test",
null,
Object.defineProperty({}, "passive", {
get: function () {
passiveSupported = true;
},
})
);
} catch (err) {}
/* リスナーを登録 */
var elem = document.getElementById("elem");
elem.addEventListener(
"touchmove",
function listener() {
/* do something */
},
passiveSupported ? { passive: true } : false
);
Passive event listener を jQuery で対応するには…
現状、ない。
おわり
要素検出や要素固定など本来スクロールに直接関係のない処理については、Intersection Observer
やposition: sticky
の登場でスクロールイベント内で処理をせず、負荷の少ない実装をする事が可能になった。
だが、それでもスクロールイベントでの処理が必要な場合は少なくない。そういう場合はこういった手法を使うのが定石となってくるだろう。