デフォルトでタップ スクロールを高速にする

Dave Tapuska
Dave Tapuska

スクロールの応答性は、モバイル ウェブサイトでのユーザー エンゲージメントにとって重要ですが、タップ イベント リスナーが原因でスクロールのパフォーマンスに深刻な問題が発生することがよくあります。Chrome では、タッチイベント リスナーを非アクティブにすること({passive: true} オプションを addEventListener() に渡す)と、ポインタ イベント API をリリースすることで、この問題に対処しています。これらの機能は、スクロールをブロックすることなくモデルに新しいコンテンツを追加するのに適していますが、デベロッパーが理解して採用するのが難しい場合があります。

Google は、デベロッパーがブラウザの動作の詳細を理解しなくても、デフォルトでウェブが高速であるべきだと考えています。Chrome 56 では、デベロッパーの意図に最もよく一致する場合、タップ リスナーをデフォルトでパッシブに設定しています。これにより、サイトへの悪影響を最小限に抑えながら、ユーザー エクスペリエンスを大幅に改善できると考えています。

まれに、この変更により意図しないスクロールが発生することがあります。通常、スクロールが発生しない要素に touch-action: none スタイルを適用することで、この問題は簡単に解決できます。詳細、影響を受けるかどうかを確認する方法、対処方法については、以下をご覧ください。

背景: キャンセル可能なイベントがページの速度を低下させる

touchstart イベントまたは最初の touchmove イベントで preventDefault() を呼び出すと、スクロールがブロックされます。問題は、ほとんどの場合、リスナーが preventDefault() を呼び出さないことです。ブラウザは、イベントが終了したことを確認するために、そのことを待つ必要があります。デベロッパーが定義する「パッシブ イベント リスナー」でこの問題を解決できます。イベント ハンドラの 3 番目のパラメータとして {passive: true} オブジェクトを指定してタップイベントを追加すると、touchstart リスナーが preventDefault() を呼び出さないことをブラウザに指示し、ブラウザはリスナーをブロックすることなく安全にスクロールを実行できます。次に例を示します。

window.addEventListener("touchstart", func, {passive: true} );

介入

主な目的は、ユーザーが画面に触れた後にディスプレイを更新する時間を短縮することです。touchstart と touchmove の使用状況を把握するため、スクロール ブロッキング動作が発生する頻度を判断する指標を追加しました。

ルート ターゲット(ウィンドウ、ドキュメント、ボディ)に送信されたキャンセル可能なタップ イベントの割合を調べたところ、これらのリスナーの約 80% は概念的にはパッシブであるものの、そのように登録されていないことが判明しました。この問題の規模を考慮し、これらのイベントを自動的に「非同期」にすることで、デベロッパーの操作なしでスクロールを改善する大きな機会があることに気づきました。

そのため、タップ開始リスナーまたはタップ移動リスナーのターゲットが windowdocumentbody の場合は、デフォルトで passivetrue に設定するように介入を定義しました。つまり、次のようなコードは

window.addEventListener("touchstart", func);

は次と同等になります。

window.addEventListener("touchstart", func, {passive: true} );

これで、リスナー内の preventDefault() への呼び出しは無視されます。

次のグラフは、ユーザーが画面をタップしてスクロールしてからディスプレイが更新されるまでの時間のうち、上位 1% のスクロールに要した時間を示しています。このデータは、Android 版 Chrome のすべてのウェブサイトに適用されます。介入を有効にする前は、スクロールの 1% が 400 ミリ秒を超えていました。Chrome 56 ベータ版では、この時間が 250 ミリ秒強に短縮され、約 38% 減少しました。今後、すべての touchstart リスナーと touchmove リスナーで passive true をデフォルトにして、この時間を 50 ms 未満に短縮したいと考えています。

上位 1% のスクロール時間のグラフ

故障とガイダンス

ほとんどの場合、破損は発生しません。ただし、破損が発生した場合、最も一般的な症状は、不要なときにスクロールが発生することです。まれに、予期しないクリック イベントが発生することもあります(touchend リスナーに preventDefault() がない場合)。

Chrome 56 以降では、介入が有効になっているイベントで preventDefault() を呼び出すと、DevTools に警告が記録されます。

touch-passive.html:19 Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080

アプリは、preventDefault の呼び出しが defaultPrevented プロパティを介してなんらかの影響を与えたかどうかを確認することで、この問題が実際に発生しているかどうかを判断できます。

影響を受けるページのほとんどは、可能な限り touch-action CSS プロパティを適用することで比較的簡単に修正できることがわかりました。要素内のブラウザのスクロールとズームをすべて禁止するには、その要素に touch-action: none を適用します。横方向のカルーセルがある場合は、touch-action: pan-y pinch-zoom を適用することを検討してください。これにより、ユーザーは通常どおり縦方向にスクロールしてズームできます。タップイベントではなくポインタ イベントをサポートするデスクトップ Edge などのブラウザでは、すでに touch-action を正しく適用する必要があります。タップ操作をサポートしていないモバイル Safari や古いモバイル ブラウザでは、タップ リスナーは、Chrome によって無視される場合でも preventDefault を呼び続ける必要があります。

より複雑なケースでは、次のいずれかにも依存する必要がある場合があります。

  • touchstart リスナーが preventDefault() を呼び出す場合は、関連する touchend リスナーからも preventDefault() が呼び出されるようにして、クリック イベントやその他のデフォルトのタップ動作の生成を抑制し続けるようにしてください。
  • 最後に(推奨されない方法ですが){passive: false} を addEventListener() に渡して、デフォルトの動作をオーバーライドします。ユーザー エージェントが EventListenerOptions をサポートしているかどうかを機能検出する必要があります。

まとめ

Chrome 56 では、多くのウェブサイトでスクロールが大幅に速く開始されます。これが、この変更によってほとんどの開発者が認識する唯一の影響です。意図しないスクロールが発生することがあります。

モバイル Safari では引き続き必要ですが、Chrome では保証されなくなったため、touchstart リスナーと touchmove リスナー内で preventDefault() を呼び出すことに依存しないでください。デベロッパーは、スクロールとズームを無効にする必要がある要素に touch-action CSS プロパティを適用し、タップ イベントが発生する前にブラウザに通知する必要があります。タップのデフォルトの動作(クリック イベントの生成など)を抑制するには、touchend リスナー内で preventDefault() を呼び出します。