スクロールジャンクを防ぐ “Passive Event Listener”とは

こんにちは、katoriです。
スマホでhoverアクションを適用するかどうか、意見が分かれる部分だと思いますが適用したいという人も多いかと思います。

この間試しに適用して検証してみたところ、Chromeの開発者ツールで大量の「[Violation] (違反)」という警告が!
びっくりして色々調べたので、アウトプットしたいと思います。

ontouchstartによるスマホでのhoverアクションの有効化

これまでスマホではhoverアクションを使わない方向でコーディングしていたので、まずどう書けばhoverを表現できるのかを検索しました。
するとすぐ何件かヒット。

どうやらHTMLに「ontouchstart」のイベントハンドラを書くと、その要素に対してCSSで記述していたhoverアクションが効くとのことでした。
イベントハンドラはその要素を内包する要素に記述しても問題ないため、例えば全てのhoverを有効にしたいなら<body ontouchstart=””> でOKなようです。
実際に試してみたところ、本当にこれだけでスマホでhoverアクションが表現できました。

しかしながら、開発者ツールのコンソールに次のような警告がでるようになりました。

スクロールジャンクの警告メッセージ

全文はこちら。

[Violation] Added non-passive event listener to a scroll-blocking ‘touchstart’ event. Consider marking event handler as ‘passive’ to make the page more responsive. See https://www.chromestatus.com/feature/5745543795965952

Google日本語訳)
[違反] 非受動的なイベントリスナーをスクロールブロッキングの「touchstart」イベントに追加しました。ページをより敏感にするには、イベントハンドラを「passive」としてマークすることを検討してください。

スクロールブロッキングもpassiveもいまいちピンと来ず…
メッセージを見ても何についてどう注意されているか分からなかったため、Chrome公式ページなどからも調べたところ、以下のような内容でした。

  • 主にスマホでページをスクロールする際、「画面のスクロールが指の動きについてこない、遅延がある」といった現象をスクロールジャンクと言う
  • 警告メッセージは、「現状だとこのスクロールジャンクが起きる可能性があるから、回避したければPassive Event Listenerを使ってね!」とのこと

なぜスクロールジャンクが起きるのか

タッチイベントで何かの関数を実行する際、スクロールを止める処理(preventDefault())をいれることってありますよね。
ブラウザ側ではスクロールする時、

  1. 登録されたタッチイベントの中にpreventDefault()がないかをチェック
  2. 無いことが確認できてからスクロールを開始

という仕様になっています。

そのため、その確認が終えるまではスクロールが開始できないため処理が詰まる=スクロールジャンクが発生する、という流れのようです。

公式にスクロールジャンクが発生している端末とそうでないものを並べてスクロールする比較検証動画があげられていました。

遅延の度合いは処理の長さによるようですが、こちらを見ると中々快適とは言えない感じですね。

Passive Event Listenerを使用するには

このスクロールジャンクを防ぐにはイベントリスナー登録時に、「この処理内ではpreventDefault()を使わないよ!」という宣言をします。そうすると、ブラウザは処理を全て確認した後ではなくすぐにスクロールに移ってくれます。

その書き方がこちら。

document.addEventListener('touchstart', handler, {passive: true});

必要なのは第三引数の{passive: true}の部分です。
document部分は、touchstartを設定したい要素と差し替えてもOK。

ちなみにtouchstartの他だとwheel、mousewheel、touchmoveあたりでPassive Event Listenerの設定が求められます。

実際に試してみる

先ほどのスマホhover有効化のためにontouchstartを使ったコードを簡易化し、先ほどの警告が本当に出なくなるのか確かめてみました。

まずontouchstartを追加せず、cssでhoverをスタイリングしただけの状態。

<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<title>検証</title>
</head>
<body>
	<p>hover要素</p>
	<style>
		p { background: #fff; }
		p:hover{ background: red; }
	</style>
</body>
</html>

[PC] オンマウスでhoverアクションが実行される
[スマホ] タップしてもhoverアクションは行われない
[コンソール] 特にメッセージなし

次に、先ほどのコードのbodyにontouchstartを追加した状態。

<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<title>検証</title>
</head>
<body ontouchstart="">
	<p>hover要素</p>
	<style>
		p { background: #fff; }
		p:hover{ background: red; }
	</style>
</body>
</html>

[PC][スマホ] hoverアクションが実行される
[コンソール] 警告が表示される([Violation] Added non-passive event listener to a scroll-blocking ‘touchstart’ event. 〜)

「ontouchstart」を追加したことで警告が表示されました。

最後に、bodyのontouchstartを削除して、JSにdocument.addEventListener(‘touchstart’, function() {}, {passive: true}); を追加した状態。

<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<title>検証</title>
</head>
<body>
	<p>hover要素</p>
	<style>
		p { background: #fff; }
		p:hover{ background: red; }
	</style>
	<script>
		document.addEventListener('touchstart', function() {}, {passive: true});
	</script>
</body>
</html>

[PC][スマホ] hoverアクションが実行される
[コンソール] 特にメッセージなし

無事警告が解消されることが確認できました!

ついでにhtmlのontouchstart=””の記述の中でpassiveオプションを指定できないかな〜と思って調べてみましたが出てこず。こちらは難しそうです。

wheel、mousewheel、touchstart、touchmoveのイベントには注意しよう

今回はスクロールジャンクに悩まされて…という訳ではないですが、これまでスクロールがぎこちなく感じてたものの要因の一つを知ることができ、警告が消えたことですっきりしました( ´∀`)

chromeの開発者ツールのメッセージは、拾えば拾うほどコンテンツのクオリティが洗練されていくようで楽しいなぁと思います。

以上、スクロールジャンクを防ぐ “Passive Event Listener”についてでした。

今回とは逆に、{passive: false}を使ったスクロール禁止のJavaScriptに関する記事もありますので、よければご覧ください。

参考サイト