次のペイントに対するインタラクションを最適化する

ウェブサイトの Interaction to Next Paint を最適化する方法をご確認ください。

Interaction to Next Paint(INP)は Core Web Vitals の安定している Core Web Vitals 指標で、ユーザーによるページ訪問までのすべての対象となるインタラクションのレイテンシを監視することで、ユーザー インタラクションに対するページの全体的な応答性を評価します。最終的な INP 値は、観測された最長インタラクションとなります(場合によっては外れ値を無視します)。

優れたユーザー エクスペリエンスを提供するため、ウェブサイトは Interaction to Next Paint を 200 ミリ秒以下に維持する必要があります。ほとんどのユーザーに対してこの目標値を確実に達成するには、モバイル デバイスとデスクトップ デバイスで分けたページ読み込みの 75 パーセンタイルを測定することをおすすめします。

INP の良好な値は 200 ミリ秒以下で、低い値は 500 ミリ秒を超えており、その中間の値には改善が必要です。

ウェブサイトによっては、インタラクションがほとんどまたはまったく発生しない場合があります。たとえば、インタラクティブな要素がほとんどまたはまったくないテキストや画像のページがほとんどです。また、テキスト エディタやゲームなどのウェブサイトの場合、何百、何千ものインタラクションが発生することもあります。いずれの場合も、INP が高い場合、ユーザー エクスペリエンスは危険にさらされます。

INP の改善には時間と労力がかかりますが、そのメリットはユーザー エクスペリエンスの向上です。このガイドでは、INP を改善する道筋を示します。

INP 低下の原因を特定する

遅いインタラクションを修正するには、ウェブサイトの INP が低いか、改善が必要かを示すデータが必要です。これらの情報を把握したら、ラボに進み、遅いインタラクションの診断を開始し、解決策に向けて取り組みます。

現場で遅いインタラクションを見つける

理想的には、INP の最適化の取り組みはフィールド データから始めるのが理想的です。実際のユーザー モニタリング(RUM)プロバイダから提供されるフィールド データは、ページの INP 値だけでなく、INP 値自体の原因となった特定のインタラクション、ページの読み込み中または読み込み後のインタラクションの種類(クリック、キー入力、タップ)、その他の有用な情報を示すコンテキスト データも提供します。

フィールド データの取得を RUM プロバイダに依存していない場合は、INP フィールド データガイドで、PageSpeed Insights の Chrome ユーザー エクスペリエンス レポート(CrUX)を使用してギャップを埋めることが推奨されています。CrUX は Core Web Vitals プログラムの公式データセットであり、INP を含む何百万ものウェブサイトの指標の概要を提供します。ただし、CrUX では多くの場合、問題の分析に役立つ RUM プロバイダから取得したコンテキスト データが提供されません。そのため、可能であれば RUM プロバイダを使用するか、CrUX で利用可能な機能を補完するために独自の RUM ソリューションを実装することをおすすめします。

ラボでの遅いインタラクションの診断

インタラクションが遅いことを示すフィールド データが得られたら、ラボでテストを開始するのが理想的です。フィールド データがない場合、ラボでは低速なインタラクションを特定するための戦略がいくつかあります。そうした戦略には、一般的なユーザーフローをフォローしてその過程でインタラクションをテストすることや、メインスレッドが最もビジー状態になることが多い読み込み中のページとのやり取りなど、ユーザー エクスペリエンスの重要な部分で遅いインタラクションを明らかにするためのものが含まれます。

インタラクションを最適化する

遅いインタラクションを特定し、ラボで手動で再現できたら、次のステップとして最適化を行います。インタラクションは、次の 3 つのフェーズに分類できます。

  1. 入力遅延: ユーザーがページに対するインタラクションを開始すると開始され、インタラクションのイベント コールバックの実行が開始されると終了します。
  2. 処理時間。イベント コールバックの実行が完了するまでにかかる時間が含まれます。
  3. プレゼンテーションの遅延。ブラウザが操作の視覚的な結果を含む次のフレームを表示するのにかかる時間。

これら 3 つのフェーズの合計がインタラクション レイテンシの合計です。インタラクションのどのフェーズも、インタラクションの合計レイテンシにある程度の時間は関係します。そのため、インタラクションの各部分を最適化して、実行時間を最小限に抑える方法を把握することが重要です。

入力遅延を特定して低減する

ユーザーがページを操作すると、その最初の部分が「入力遅延」です。ページの他のアクティビティによっては、入力遅延がかなり長くなる場合があります。この原因としては、メインスレッドでのアクティビティ(スクリプトの読み込み、解析、コンパイルなど)、フェッチ処理、タイマー関数、または短時間に連続して発生し互いに重複する他の操作が原因の可能性があります。

インタラクションの入力遅延の原因がどのようなものであっても、インタラクションができるだけ早くイベント コールバックの実行を開始できるように、入力遅延を最小限に抑える必要があります。

起動時のスクリプト評価と長時間タスクの関係

起動時は、ページのライフサイクルにおけるインタラクティビティの重要な要素です。ページが読み込まれると、最初にレンダリングが行われますが、ページのレンダリングが完了したからといって、ページの読み込みが完了したとは限りません。ページが完全に機能するために必要なリソースの数によっては、読み込み中にユーザーがページを操作しようとする可能性があります。

ページの読み込み中にインタラクションの入力遅延を延ばす方法として、スクリプトの評価が挙げられます。JavaScript ファイルがネットワークから取得された後、ブラウザは JavaScript を実行可能にするためにまだ作業が必要です。その作業には、スクリプトを解析して構文が有効であることを確認し、ファイルをバイトコードにコンパイルして、最終的に実行することが含まれます。

スクリプトのサイズによっては、この処理により長いタスクがメインスレッドに実行され、ブラウザが他のユーザー操作に応答できなくなる可能性があります。ページの読み込み時にユーザー入力に対するページの応答性を維持するには、ページの読み込み中に長時間かかることを避けて、快適な動作を維持するために何ができるかを理解することが重要です。

イベント コールバックを最適化する

入力遅延は、INP が測定する最初の部分にすぎません。また、ユーザー操作への応答として実行されるイベント コールバックができるだけ速く完了するようにする必要もあります。

頻繁にメインスレッドに移る

イベント コールバックの最適化に関する一般的なアドバイスは、処理をできる限り少なくすることです。ただし、インタラクションのロジックが複雑で、実行する作業をわずかに減らすことができない場合もあります。

ご自身のウェブサイトがこのケースに該当することがわかったら、次に行うべきことは、イベント コールバックでの作業を別々のタスクに分割することです。これにより、集合的な処理がメインスレッドをブロックする長いタスクになることを防ぎ、メインスレッドで待機していた他のインタラクションをより早く実行できるようになります。

setTimeout は、渡されたコールバックが新しいタスクで実行されるため、タスクを分割する方法の 1 つです。setTimeout を単独で使用するか、その使用を別の関数に抽象化して人間工学的に優れた収率を達成できます。

無差別に値を渡すほうが、まったく譲らないよりも効果的です。ただし、メインスレッドに譲る場合は、より微妙な方法があります。その場合は、ユーザー インターフェースを更新するイベント コールバックの直後にのみ id を渡して、レンダリング ロジックをより早く実行します。

レンダリング処理をより早く行えるようにするイールド

より高度な結果を得るための手法として、イベント コールバックのコードを構造化し、次のフレームの視覚的な更新を適用するために必要なロジックのみを実行するように制限します。それ以外はすべて、後続のタスクに延期できます。これにより、コールバックが軽量で機敏に保たれるだけでなく、イベント コールバック コードの視覚的な更新をブロックできなくなるので、操作のレンダリング時間も短縮されます。

たとえば、入力に応じてテキストを書式設定するだけでなく、記述内容に応じて UI の他の要素(文字カウント、スペルミスのハイライト表示、その他の重要な視覚的なフィードバックなど)を更新するリッチテキスト エディタを想像してみてください。また、離れたり戻ってきても作業内容が失われないように、記述した内容を保存する必要が生じる場合もあります。

この例では、ユーザーが文字を入力したときに、次の 4 つの処理を行う必要があります。ただし、次のフレームが表示される前に、最初のアイテムのみを完了する必要があります。

  1. ユーザーが入力した内容でテキスト ボックスを更新し、必要な書式を適用します。
  2. 現在の単語カウントを表示する UI の部分を更新します。
  3. スペルミスをチェックするロジックを実行します。
  4. 最新の変更をローカルまたはリモート データベースに保存します。

これを行うためのコードは次のようになります。

textBox.addEventListener('input', (inputEvent) => {
  // Update the UI immediately, so the changes the user made
  // are visible as soon as the next frame is presented.
  updateTextBox(inputEvent);

  // Use `setTimeout` to defer all other work until at least the next
  // frame by queuing a task in a `requestAnimationFrame()` callback.
  requestAnimationFrame(() => {
    setTimeout(() => {
      const text = textBox.textContent;
      updateWordCount(text);
      checkSpelling(text);
      saveChanges(text);
    }, 0);
  });
});

以下の可視化は、重要でない更新を次のフレームまで延期することで、処理時間を削減し、全体的なインタラクション レイテンシを短縮できることを示しています。

2 つのシナリオでのキーボード操作と後続のタスクの描写。上の図では、フレームを表示するタイミングになるまで、レンダリング クリティカルなタスクと後続のすべてのバックグラウンド タスクが同期的に実行されています。下の図では、レンダリング クリティカルな処理が最初に実行され、次にメインスレッドに移行して新しいフレームが早く表示されています。その後、バックグラウンド タスクが実行されます。
上の図をクリックすると、高解像度のバージョンが表示されます。

前のコードサンプルの requestAnimationFrame() 呼び出し内で setTimeout() を使用しているのは少々難解ですが、これは重要でないコードが次のフレームをブロックしないように、すべてのブラウザで機能する効果的な方法です。

レイアウト スラッシングを回避する

レイアウト スラッシング(強制同期レイアウトとも呼ばれます)は、レイアウトが同期的に発生するレンダリング パフォーマンスの問題です。これは、JavaScript でスタイルを更新してから同じタスクで読み取る場合に発生します。レイアウト スラッシングの原因となるプロパティが JavaScript に多数あります

Chrome DevTools のパフォーマンス パネルに表示されているレイアウト スラッシングの可視化。
Chrome DevTools のパフォーマンス パネルに表示されるレイアウト スラッシングの例。レイアウト スラッシングを含むレンダリング タスクは、コールスタックの右上に赤い三角形で表示されます。この三角形は多くの場合、Recalculate StyleLayout というラベルが付いています。

レイアウト スラッシングはパフォーマンスのボトルネックになります。スタイルを更新してすぐに JavaScript でそれらのスタイルの値をリクエストすると、ブラウザは同期的なレイアウト作業を行わざるを得なくなります。

プレゼンテーションの遅延を最小化

インタラクション マークのプレゼンテーション遅延は、インタラクションのイベント コールバックの実行が終了してから、ブラウザが視覚的な変化を示す次のフレームを描画できる時点までを指します。

DOM サイズを最小化する

通常、ページの DOM が小さいと、レンダリング作業はすぐに終了します。ただし、DOM が非常に大きくなると、DOM のサイズが大きくなるにつれてレンダリング処理がスケールする傾向があります。レンダリング処理と DOM のサイズとの関係は直線的ではありませんが、大規模な DOM は小規模な DOM よりもレンダリングに多くの作業を必要とします。大きな DOM には、次の 2 つのケースで問題が発生します。

  1. 最初のページのレンダリング時に、大きな DOM ではページの初期状態をレンダリングするために多くの作業が必要になります。
  2. DOM が大きいためにレンダリング アップデートが非常に高コストになり、ブラウザが次のフレームを表示するのにかかる時間が長くなる可能性があるユーザー操作への応答。

状況によっては、大規模な DOM を大幅に削減できない場合があります。DOM のサイズを縮小するには、DOM をフラット化する、ユーザーによる操作中に DOM に追加して初期 DOM サイズを小さくするなど、いくつかのアプローチがありますが、これらの手法では限界があります。

content-visibility を使用して画面外の要素のレンダリングを遅延させる

ページの読み込み時のレンダリング処理と、ユーザーの操作に応じたレンダリング処理の両方の量を制限する方法の 1 つは、CSS の content-visibility プロパティを活用することです。これにより、要素がビューポートに近づくにつれて効果的に遅延が発生します。content-visibility を効果的に使用するにはある程度の練習が必要ですが、レンダリング時間が短くなり、ページの INP が向上する可能性があるかどうかを調べることをおすすめします。

JavaScript を使用して HTML をレンダリングする場合のパフォーマンス コストに注意する

HTML には HTML の解析があります。ブラウザは、DOM への HTML の解析を終えた後、スタイルを適用し、レイアウト計算を実行して、そのレイアウトをレンダリングする必要があります。これは避けられないコストですが、HTML をレンダリングする方法が重要です。

サーバーが HTML を送信すると、ブラウザにストリームとして到達します。ストリーミングとは、サーバーからの HTML レスポンスがチャンク形式で届くことを指します。ブラウザは、ストリームの受信時にチャンクを段階的に解析し、ビットを少しずつレンダリングすることで、ストリームの処理方法を最適化します。これは、ブラウザがページ読み込み中に定期的かつ自動的に結果を出力するという点でパフォーマンスの最適化であり、料金はかかりません。

ウェブサイトへの初回アクセスには常にある程度の HTML が必要になりますが、一般的なアプローチでは、HTML の初期部分を最小限に抑え、JavaScript を使用してコンテンツ領域にデータを入力します。そのコンテンツ領域のその後の更新も、ユーザーの操作の結果として行われます。これは通常、シングルページ アプリケーション(SPA)モデルと呼ばれます。このパターンの欠点の 1 つは、クライアントで JavaScript を使用して HTML をレンダリングすると、その HTML を作成するための JavaScript 処理のコストが発生するだけでなく、ブラウザがその HTML の解析とレンダリングを完了するまで収益を得られないことです。

ただし、SPAではないウェブサイトであっても、相互作用の結果として JavaScript を介したある程度の HTML レンダリングが必要になる可能性があるということを覚えておいてください。クライアントで大量の HTML をレンダリングして次のフレームの表示を遅らせる場合以外は、通常はこれで問題ありません。ただし、このアプローチがブラウザでの HTML のレンダリングのパフォーマンスに与える影響と、JavaScript を使用して大量の HTML をレンダリングする場合に、ユーザー入力に対するウェブサイトの応答性にどのような影響があるかを理解することが重要です。

おわりに

サイトの INP の改善は反復的なプロセスです。ウェブサイトで遅いインタラクションを修正すると、特にウェブサイトのインタラクションが多い場合、他の遅いインタラクションが見つかる可能性があり、それらも最適化する必要がある可能性が高くなります。

INP を改善するための鍵は、持続性です。やがては、提供するエクスペリエンスにユーザーが満足する場所に、ページの応答性を向上させることができるでしょう。また、ユーザー向けに新機能を開発するときには、同じプロセスを踏んでユーザー固有のインタラクションを最適化する必要がある可能性もあります。これには時間と労力がかかりますが、その時間と労力は有意義なものになっています。

David Pisnoy 著「Unsplash」のヒーロー画像。Unsplash ライセンスに従って編集。