読み込みパフォーマンスと入力の応答性のトレードオフを回避できる新しい 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 ウェブ パフォーマンス ワーキング グループの他のメンバーからのフィードバックを受け、API の変更を実装しました。
例: より高い割り当てを行うスケジューラ
ページの読み込みに、コンポーネントからのマークアップの生成、素数の因数分解、クールな読み込みスピナーの描画など、表示をブロックする作業が多数あるとします。これらの各タスクは個別の作業項目に分割されます。スケジューラ パターンを使用して、架空の 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 を構築し、アイデアの検討から標準提案、実際にブラウザでリリースするまでのプロセスを初めて行ったものです。ここまで到達できたのは、皆様のご協力のおかげです。このアイデアを具体化してリリースに至るまでサポートしてくれた Chrome の皆様には、特に感謝の意を表します。
ヒーロー写真: Will H McMahan(Unsplash)