スクロールジャンクを防ぐ “Passive Event Listener”とは
こんにちは、katori@xxbicotです。
スマホでhoverアクションを適用するかどうか。ユーザビリティ的な考えだと適用しない方が多数かな、と思いますが、適用したいという人もいるかと思います。
この間、スマホでのhoverアクションを初めてテストする機会があったんですが、Chromeの開発者ツールでばんばん警告が出てびっくりしました。
大量の警告、そして[Violation] (「違反」)という文字にびびって色々調べたのでアウトプットしていきます。
Contents
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())をいれることってありますよね。
ブラウザ側はスクロールする際、登録されたタッチイベントの中にpreventDefault()がないかをチェックし、無いことが確認できてからスクロールを開始するような仕様になっています。
逆に言うと、その確認が終えるまではスクロールが開始できないため処理が詰まる=スクロールジャンクが発生する、という流れのようです。
公式にスクロールジャンクが発生している端末とそうでないものを並べてスクロールする比較検証動画があげられていました。
遅延の度合いは処理の長さによるようですが、こちらを見ると中々快適とは言えない感じですね。
でもなんだか既視感があるので、どこかでこんなサイトを見たか作ったかもしれない…(~_~;)
Passive Event Listenerを使用するには
このスクロールジャンクを防ぐにはイベントリスナー登録時に、「この処理内では”preventDefault()”を使わないよ!」という宣言をします。そうすると、ブラウザは処理を全て確認した後ではなくすぐにスクロールに移ってくれます。
その書き方がこちら。
document.addEventListener('touchstart', handler, {passive: true});
必要なのは第三引数の{passive: true}の部分です。
document部分は、touchstartを設定したい要素と差し替えてもOK。
ちなみにtouchstartの他だとwheel、mousewheel、touchmoveあたりのイベントリスナーで、スクロールキャンセル処理をいれないものには{passive: true}を設定すると良いとのことです。
実際に試してみる
先ほどのスマホ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アクションが実行される
[コンソール] 特にメッセージなし
無事警告が解消されることが確認できました!
ちなみに、スマホのhover有効化には「ontouchstart」をHTMLの要素に追加すると書きましたが、上記の通りjsでaddEventListenerを書く方法でも問題ないことを確認しています。
スマホhover有効化目的であればjsで書いておくと良さそうですね。
ついでにhtmlのontouchstart=””の記述の中でpassiveオプションを指定できないかなーと思ったんですが、こちらは調べても出てきませんでした。。
(英語の記事でjsxというものを使った場合でそれに近い書き方を見かけましたが、Reactはよくわからないので割愛)
wheel、mousewheel、touchstart、touchmoveのイベントには注意しよう
今回はスクロールジャンクに悩まされて…という訳ではないですが、これまでスクロールがぎこちなく感じてたものの要因の一つを知ることができたし、警告が消えたことですっきりしました( ´∀`)
chromeの開発者ツールのメッセージは、拾えば拾うほどコンテンツのクオリティが洗練されていくのを感じるので結構好きです。
あんまり触れてこなかった知識も、こういった警告を深掘りするとなんとなく分かるのが楽しいですね。
以上、スクロールジャンクを防ぐ “Passive Event Listener”についてでした。