新しい JavaScript API を使用すると、読み込みパフォーマンスと入力応答性のトレードオフを回避できます。
高速読み込みは難しいものです。JS を使用してコンテンツをレンダリングするサイトでは、現在、読み込みパフォーマンスと入力応答性の間でトレードオフをする必要があります。表示に必要なすべての作業を一度に実行する(読み込みパフォーマンスの向上、入力応答性の低下)か、入力とペイントへの応答性を維持するために作業を小さなタスクに分割する(読み込みパフォーマンスが悪く、入力の応答性が良い)。
このトレードオフを排除するため、Facebook は Chromium に isInputPending()
API を提案し、実装しました。これにより、譲渡することなく応答性を向上させることができます。オリジン トライアルのフィードバックに基づいて API を複数回更新し、Chromium 87 で API がデフォルトで提供されるようになりました。
ブラウザの互換性
isInputPending()
は、バージョン 87 以降の Chromium ベースのブラウザでリリースされています。どのブラウザでも、API を配布する意思は示されていません。
背景
現在の JS エコシステムでは、ほとんどの処理がメインスレッドという単一のスレッドで実行されます。これにより、デベロッパーは堅牢な実行モデルを利用できますが、スクリプトが長時間実行されると、ユーザー エクスペリエンス(特に応答性)が大幅に低下する可能性があります。たとえば、入力イベントの発生中にページで大量の処理が行われている場合、その処理が完了するまではクリック入力イベントを処理しません。
現在のベスト プラクティスは、JavaScript を小さなブロックに分割してこの問題に対処することです。ページの読み込み中に、ページは少しの JavaScript を実行し、ブラウザに制御を渡すことができます。ブラウザは入力イベント キューをチェックし、ページに伝える必要があるものがないか確認できます。その後、ブラウザは追加された JavaScript ブロックの実行に戻ることができます。これは役立ちますが、他の問題を引き起こす可能性があります。
ページがブラウザに制御を返すたびに、ブラウザが入力イベントキューをチェックし、イベントを処理して、次の JavaScript ブロックを取得するまでに時間がかかります。ブラウザはイベントに迅速に応答しますが、ページの全体的な読み込み時間は遅くなります。頻繁に降伏すると、ページの読み込みが遅くなります。生成される頻度が下がると、ブラウザがユーザー イベントに応答するまでの時間が長くなり、ユーザーは不満を抱きます。楽しくなかった。
Facebook では、このような面倒なトレードオフを解消できる読み込みの新しいアプローチを思いついたら、どんなことができるのかと考えました。この点について Chrome の仲間たちに聞き取り、isInputPending()
を提案しました。isInputPending()
API は、ウェブでのユーザー入力に割り込みのコンセプトを初めて使用し、JavaScript がブラウザに譲渡することなく入力を確認できるようにします。
この API に関心が寄せられたため、Chrome の同僚と連携して Chromium にこの機能を実装し、リリースしました。Chrome のエンジニアの力を借りて、オリジン トライアル(API を完全にリリースする前に Chrome で変更をテストしてデベロッパーからフィードバックを得るための方法)でパッチを公開しました。
現在、オリジン トライアルと W3C Web Performance Working Group の他のメンバーからのフィードバックを参考に、API の変更を実施しています。
例: yiey スケジューラ
ページの読み込みに、コンポーネントからのマークアップの生成、素数の因数分解、クールな読み込みスピナーの描画など、表示をブロックする作業が多数あるとします。これらの各タスクは個別の作業項目に分割されます。スケジューラ パターンを使用して、架空の processWorkQueue()
関数で処理を行う方法を概説します。
const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
if (performance.now() >= DEADLINE) {
// Yield the event loop if we're out of time.
setTimeout(processWorkQueue);
return;
}
let job = workQueue.shift();
job.execute();
}
setTimeout()
を介して新しいマクロタスクの後半で processWorkQueue()
を呼び出すことで、ブラウザは比較的中断されることなく実行しながら、入力にもある程度応答できるようになります(処理が再開される前にイベント ハンドラを実行できます)。ただし、イベントループの制御を必要とする他の処理によって、スケジュールが長時間キャンセルされるか、イベント レイテンシが最大 QUANTUM
ミリ秒追加される可能性があります。
どうでしょうか?もちろんです
const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
if (navigator.scheduling.isInputPending() || performance.now() >= DEADLINE) {
// Yield if we have to handle an input event, or we're out of time.
setTimeout(processWorkQueue);
return;
}
let job = workQueue.shift();
job.execute();
}
navigator.scheduling.isInputPending()
の呼び出しを導入することで、ディスプレイのブロック作業が中断されることなく実行される一方で、入力に迅速に対応できるようになりました。処理が完了するまで入力(ペイントなど)以外の処理を行う必要がない場合は、QUANTUM
の長さを簡単に増やすこともできます。
デフォルトでは、isInputPending()
から「連続」イベントは返されません。たとえば、mousemove
、pointermove
などです。これらの動画の収益化にもご興味をお持ちの場合は、お気軽にお問い合わせください。includeContinuous
を true
に設定したオブジェクトを isInputPending()
に渡すと、準備が整います。
const DEADLINE = performance.now() + QUANTUM;
const options = { includeContinuous: true };
while (workQueue.length > 0) {
if (navigator.scheduling.isInputPending(options) || performance.now() >= DEADLINE) {
// Yield if we have to handle an input event (any of them!), or we're out of time.
setTimeout(processWorkQueue);
return;
}
let job = workQueue.shift();
job.execute();
}
これで、React などのフレームワークでは、同様のロジックを使用して、コア スケジューリング ライブラリに isInputPending()
サポートを構築しています。これにより、これらのフレームワークを使用するデベロッパーは、大幅な書き換えを行うことなく、バックグラウンドで isInputPending()
のメリットを享受できるようになります。
譲歩は必ずしも悪いことではありません
ただし、より少ない量を割り当てることがすべてのユースケースに適したソリューションであるとは限りません。入力イベントを処理する以外に、レンダリングやページ上の他のスクリプトの実行など、ブラウザに制御を返す理由はさまざまです。
ブラウザが保留中の入力イベントを適切にアトリビューションできないことがあります。特に、クロスオリジン iframe に複雑なクリップとマスクを設定すると、誤検出が発生する可能性があります(つまり、これらのフレームをターゲットにすると、isInputPending()
が予期せず false を返す可能性があります)。サイトがスタイル設定されたサブフレームとのインタラクションを必要とする場合は、十分な頻度でイジェクトするようにしてください。
イベントループを共有する他のページにも注意してください。Chrome for Android などのプラットフォームでは、複数のオリジンがイベントループを共有することが一般的です。入力がクロスオリジン フレームにディスパッチされた場合、isInputPending()
は true
を返すことはありません。そのため、バックグラウンド ページがフォアグラウンド ページの応答性に干渉する可能性があります。Page Visibility API を使用してバックグラウンドで処理を行う場合は、処理を減らす、延期する、または譲渡する頻度を増やすことをおすすめします。
isInputPending()
は慎重に使用してください。ユーザーをブロックする処理が不要な場合は、イベントループで他の処理に優先して、より頻繁に yield を実行します。長いタスクは有害な場合があります。
フィードバック
- 仕様に関するフィードバックは、is-input-pending リポジトリに送信します。
- Twitter で @acomminos(仕様作成者の 1 人)に連絡します。
まとめ
isInputPending()
がリリースされ、デベロッパーが本日より使用できるようになることを嬉しく思います。この API は、Facebook が初めて新しいウェブ API を構築し、アイデアの考案から標準提案の形へと移行し、実際にブラウザに実装された API です。ここまでの道のりを歩むのにご協力いただいたすべての方に感謝するとともに、このアイデアを具体化して出荷するのを支援してくれた Chrome のすべての方々に、心から感謝の意を表したいと思います。
ヒーロー写真(Will H McMahan、Unsplash より)。