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

Dave Tapuska 氏
Dave Tapuska

モバイルでのウェブサイトに対するユーザー エンゲージメントにはスクロールの応答性が不可欠ですが、タッチイベント リスナーは多くの場合にスクロールのパフォーマンスに深刻な問題を引き起こします。Chrome では、この問題に対処するために、タッチイベント リスナーをパッシブにして({passive: true} オプションを addEventListener() に渡す)、ポインタ イベント API を提供してきました。これらは、スクロールをブロックしないモデルに新しいコンテンツを取り入れるのには優れた機能ですが、デベロッパーにとって理解や採用が難しいと感じることがあります。

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

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

背景: キャンセル可能なイベントによってページの動作が遅くなる

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

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

介入

主な動機は、ユーザーが画面に触れてからディスプレイを更新するまでの時間を短縮することです。touchstart と touchmove の使用状況を理解するために、スクロール ブロック動作の発生頻度を特定する指標を追加しました。

ルート ターゲット(ウィンドウ、ドキュメント、または本体)に送信されたキャンセル可能なタッチイベントの割合を調べたところ、これらのリスナーの約 80% は概念的に受動的であるものの、そのように登録されていないことがわかりました。この問題の規模を考慮し、これらのイベントを自動的に「パッシブ」にすることで、デベロッパーの対応なしでスクロールを改善できる大きな可能性があることがわかりました。

そのため、この介入は、touchstart リスナーまたは touchmove リスナーのターゲットが windowdocument、または body である場合、デフォルトで passivetrue である場合、次のように定義することにしました。つまり、次のようなコードになります。

window.addEventListener("touchstart", func);

これは、次の式と同等になります。

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

リスナー内の preventDefault() の呼び出しが無視されるようになりました。

以下のグラフは、ユーザーが画面にタップしてからディスプレイが更新されるまでに、スクロールの上位 1% にかかった時間を示しています。このデータは Chrome for Android のすべてのウェブサイトに適用されます。介入が有効になる前は、スクロールの 1% に 400 ミリ秒強かかりました。これは Chrome 56 ベータ版では 250 ミリ秒程度にまで短縮され、約 38% 短縮されています。将来的には、パッシブをすべての touchstart リスナーと touchmove リスナーのデフォルトとして true にして、これを 50 ミリ秒未満に短縮する予定です。

上位 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

アプリケーションは、defaultPrevented プロパティを介して preventDefault の呼び出しが影響を及ぼしたかどうかを確認することで、この事象が実際に発生している可能性があるかどうかを判断できます。

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

より複雑なケースでは、次のいずれかを利用することが必要になることもあります。

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

まとめ

Chrome 56 では、多くのウェブサイトでスクロールの開始が大幅に高速化されました。この変更により、ほとんどのデベロッパーが目にする影響はこれだけです。場合によっては、デベロッパーが意図しないスクロールに気づくこともあります。

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