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

ウェブサイトの Interaction to Next Paint を最適化する方法を学びます。

Interaction to Next Paint(INP)は、ユーザーによるページアクセスの全期間中に発生するすべての対象インタラクションのレイテンシをモニタリングすることで、ユーザー インタラクションに対するページの全体的な応答性を評価するための安定した Core Web Vitals 指標です。最終的な INP 値は、最大時間を要したことがモニタリングされたインタラクションであり、外れ値は無視される場合があります。

優れたユーザー エクスペリエンスを提供するには、ウェブサイトの Interaction to Next Paint を 200 ミリ秒未満に収めるようにします。ほとんどのユーザーが閲覧する際に目標値が達成されるよう、モバイル デバイスとデスクトップ デバイスでページ読み込みの75 パーセンタイルを測定することをおすすめします。

良好な INP 値は 200 ミリ秒未満、低い値は 500 ミリ秒を超えます。その間の値は改善が必要です。

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

INP を改善するには時間と労力が必要ですが、その結果、ユーザー エクスペリエンスが向上します。このガイドでは、INP を改善するための方法について説明します。

INP が低い原因を特定する

操作の遅延を修正する前に、ウェブサイトの INP が低いか、改善が必要かを示すデータが必要です。情報を収集したら、ラボに移動してインタラクションの遅延の診断を開始し、解決策を探します。

フィールドで遅いインタラクションを見つける

理想的には、インプレッション単価の最適化は現場データから始めます。リアルユーザー モニタリング(RUM)プロバイダのフィールド データでは、ページの INP 値だけでなく、INP 値自体に影響を与えた特定のインタラクション、インタラクションが発生したタイミング(ページの読み込み中か読み込み後か)、インタラクションの種類(クリック、キープレス、タップ)などの有用なコンテキスト データも得られます。

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

ラボでインタラクションの遅延を診断する

インタラクションの遅延を示唆するフィールドデータが得られたら、ラボでテストを開始するのが理想的です。フィールドデータがない場合は、ラボで遅いインタラクションを見つけるための戦略がいくつかあります。このような戦略には、一般的なユーザーフローを追跡してその過程でインタラクションをテストする方法や、読み込み中(多くの場合、メインスレッドが最も負荷が高いとき)にページを操作して、ユーザー エクスペリエンスの重要な部分で遅いインタラクションが発生しているかどうかを特定する方法などがあります。

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

遅いインタラクションを見つけてラボで手動で再現できる場合は、次に最適化を行います。インタラクションには次の 3 つのフェーズがあります。

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

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

入力遅延を特定して短縮する

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

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

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

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

ページの読み込み中にインタラクションの入力遅延を長くする要因の一つが、スクリプトの評価です。JavaScript ファイルがネットワークから取得された後、その JavaScript を実行する前にブラウザで行う作業がまだあります。その作業には、スクリプトを解析して構文が有効であることを確認する、バイトコードにコンパイルする、最後に実行するなどがあります。

スクリプトのサイズによっては、この処理によってメインスレッドで長時間実行タスクが発生し、ブラウザが他のユーザー操作に応答するまでに遅延が生じる可能性があります。ページの読み込み中にユーザー入力に応答し続けるには、ページの読み込み中に長時間のタスクが発生する可能性を減らし、ページの応答性を維持するためにできることを理解することが重要です。

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

入力遅延は、INP が測定する項目の最初の部分にすぎません。また、ユーザー操作に応じて実行されるイベント コールバックが可能な限り迅速に完了するようにする必要があります。

メインスレッドに頻繁に譲渡する

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

ウェブサイトにこの問題がある場合は、次に、イベント コールバックの処理を個別のタスクに分割することを試します。これにより、集約処理が長時間のタスクになってメインスレッドをブロックすることを防ぎ、メインスレッドで待機していた他のインタラクションの実行を早めることができます。

setTimeout は、渡されたコールバックが新しいタスクで実行されるため、タスクを分割する 1 つの方法です。setTimeout を単独で使用することも、より人間工学的な処理を実現するために、その使用を別の関数に抽象化することもできます。

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

レンダリング作業を早期に開始できるようにする

より高度な譲渡手法では、イベント コールバックのコード構造を変更して、実行される処理を次のフレームのビジュアル アップデートに必要なロジックに限定します。その他の処理は後続のタスクに延期できます。これにより、コールバックを軽量で機敏に保つだけでなく、視覚的な更新がイベント コールバック コードをブロックしないようにすることで、インタラクションのレンダリング時間が短縮されます。

たとえば、入力した内容に応じてテキストを書式設定するだけでなく、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 のパフォーマンス パネルに表示されるレイアウト スラッシングの一例。レイアウト スラッシングを含むレンダリング タスクは、コールスタックの該当部分の右上に赤い三角形で示されます。通常は「スタイルの再計算」または「レイアウト」というラベルが付いています。

レイアウト スラッシングはパフォーマンスのボトルネックです。スタイルを更新してから JavaScript でそのスタイルの値をすぐにリクエストすると、ブラウザは同期レイアウト処理を強制的に行うことになります。この処理は、イベント コールバックの実行が完了した後で非同期に実行することもできます。

表示の遅延を最小限に抑える

インタラクション マークの表示遅延は、インタラクションのイベント コールバックの実行が完了してから、ブラウザが結果の視覚的な変更を示す次のフレームをペイントできるまでの時間です。

DOM サイズを最小限に抑える

ページの DOM が小さい場合、通常はレンダリング作業はすぐに完了します。ただし、DOM が非常に大きくなると、レンダリング作業は DOM サイズの増加に伴ってスケーリングされる傾向があります。レンダリング作業と DOM サイズの関係はリニアではありませんが、DOM が大きいほど、レンダリングに必要な作業量は多くなります。大きな DOM は、次の 2 つの場合に問題になります。

  1. ページの最初のレンダリング中。DOM が大きい場合、ページの初期状態をレンダリングするために多くの作業が必要になります。
  2. ユーザー操作に応答する場合。DOM が大きい場合、レンダリングの更新に非常に時間がかかり、ブラウザが次のフレームを表示するまでに時間がかかります。

ただし、DOM が大きい場合、大幅に削減できないこともあります。DOM をフラット化したり、ユーザー操作中に DOM に追加したりして、最初の DOM サイズを小さく保つなど、DOM サイズを削減するためのアプローチはありますが、これらの手法では限界があります。

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

ページの読み込み時とユーザー操作に応じたレンダリング処理の両方の量を制限する方法の一つは、CSS content-visibility プロパティを使用することです。これは、ビューポートに近づくにつれて要素を遅延レンダリングする効果があります。content-visibility を効果的に使用するには、ある程度の練習が必要ですが、レンダリング時間が短縮され、ページの INP が改善されるかどうかを検討する価値はあります。

詳しくは、

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

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 を改善する鍵は、粘り強さです。時間の経過とともに、ページの応答性を高め、ユーザーが提供されているエクスペリエンスに満足できる状態にすることができます。また、ユーザー向けの新しい機能を開発する際に、ユーザー固有のインタラクションを最適化するために、同じプロセスを繰り返す必要がある可能性もあります。時間と労力はかかりますが、その労力は必ず報われます。

Unsplash のヒーロー画像(David Pisnoy 撮影)、Unsplash ライセンスに基づいて変更。