INP が低い原因を特定する

インタラクションに対する応答性が低いと、ページの読み込みが遅い、または完全に壊れているという印象を与えます。Interaction to Next Paint(INP)は、Event Timing API に基づく指標です。ページの応答性の指標として、最悪のインタラクション(一部の外れ値を除く)を測定します。

応答しないウェブページの根本原因を特定することは、特にユーザーの操作や、デバイスの機能やネットワークの状態などの環境条件に依存するため、難しい場合があります。そのため、ページの読み込みなど、再現性と予測可能性が高いシナリオと比較して、診断がさらに難しくなります。ラボデータは、実際のユーザー シナリオの小さなサブセットのみを再現するため、フィールドで INP の低下の原因を測定することが不可欠です。

Event Timing API を使用すると、INP の時間を 3 つのサブパートに分割できます。入力遅延(インタラクションが発生したときにすでに発生している処理)、処理時間(インタラクションの直接的な結果として発生する遅延)、表示遅延(インタラクション後に次のフレームをレンダリングすることによる遅延)です。これにより、インタラクション コードに直接ジャンプするのではなく、問題が他のコード、インタラクション JavaScript コード、ブラウザ処理レンダリングの更新のいずれにあるかを特定できます。

JavaScript、スタイルの再計算、レイアウト作業がインタラクションを遅らせている原因について、さらに分析情報を収集できます。JS Self-Profiling API を使用した完全なパフォーマンス トレースは、パフォーマンスの問題を引き起こす可能性のある、負荷の高いソリューションです。Long Animation Frames API は、フィールドで使用できる軽量の API で、実行速度の遅い JavaScript を特定し、INP インタラクション中のスタイルの再計算とレイアウトのコストを区別できます。

  • スクリプト レベルの強制スタイルとレイアウト(forcedStyleDuration と forcedLayoutDuration): 入力遅延または処理時間中に、実行中のスクリプト内で同期スタイルの再計算(DOM の変更後に getComputedStyle() を呼び出すなど)と強制レイアウト / リフロー(変更後に getBoundingClientRect() または offsetWidth を読み取るなど)を報告します。
  • フレームレベルのレンダリング スタイルとレイアウト(styleDuration と layoutDuration): フレームのレンダリング更新中に、スタイルの再計算に費やされた時間とレイアウトの更新に費やされた時間をレポートします。これは、プレゼンテーション遅延が高い理由を説明するのに役立ちます。

スタイルの再計算とレイアウトを区別することは、根本原因と修正方法が異なるため重要です。スタイルの所要時間が長い場合は、高負荷な CSS セレクタの一致や広範なスタイルの無効化が原因です(セレクタの簡素化やルールのスコープの縮小によって修正されます)。一方、レイアウトの所要時間が長い場合は、高負荷なジオメトリ計算や同期レイアウトのスラッシングが原因です(書き込み前の DOM 読み取りのバッチ処理、DOM 構造の簡素化、CSS コンテインメントの使用によって修正されます)。

導入方法

未加工の Event Timing API から INP を計算するのは複雑で、いくつかのニュアンスがあります。このデータを収集するには、RUM ツールまたはライブラリを使用することをおすすめします。

web-vitals は、INP を含む Core Web Vitals を計算する Google のオープンソース ライブラリです。INP インタラクションのサブパートと長いアニメーション フレームのデータも含まれています。

web-vitals ライブラリを使用して INP インタラクションのアトリビューションを取得する

web-vitals ライブラリは、Core Web Vitals やその他のパフォーマンス指標を測定するために使用される小さなライブラリです。onINP() 関数を使用すると、最も遅いインタラクションを特定できます。この関数には、INP サブパート、インタラクション中に実行されたスクリプト、Long Animation Frames API を使用したスタイルとレイアウトの期間に関する情報が含まれます。

// Use the attribution build to get Long Animation Frame data
// alongside the INP metric value.
import { onINP } from 'web-vitals/attribution';

onINP((metric) => {
  const { attribution } = metric;
  const longestScript = attribution.longestScript;

  // Sum rendering-phase style and layout durations across all LoAF entries
  // intersecting the INP interaction (primarily explains presentationDelay).
  let renderStyleDuration = 0;
  let renderLayoutDuration = 0;
  for (const entry of attribution.longAnimationFrameEntries) {
    renderStyleDuration += entry.styleDuration ?? 0;
    renderLayoutDuration += entry.layoutDuration ?? 0;
  }

  // Beacon script, style, and layout attribution for the INP interaction,
  // so you can identify the root cause in production.
  navigator.sendBeacon(
    '/analytics',
    JSON.stringify({
      name: 'INP',
      value: metric.value,
      // These fields give the INP subparts:
      inputDelay: attribution.inputDelay,
      presentationDelay: attribution.presentationDelay,
      processingDuration: attribution.processingDuration,
      interactionTarget: attribution.interactionTarget,
      // These fields identify which script function was responsible
      // for the longest processing during the INP interaction.
      invokerType: longestScript?.entry?.invokerType,
      sourceURL: longestScript?.entry?.sourceURL,
      sourceFunctionName: longestScript?.entry?.sourceFunctionName,
      sourceCharPosition: longestScript?.entry?.sourceCharPosition,
      // subpart indicates which phase (input delay, processing, or
      // presentation delay) the longest script overlapped with most.
      subpart: longestScript?.subpart,
      intersectingDuration: longestScript?.intersectingDuration,
      // Synchronous style and layout work forced inside the longest script
      // (with forcedStyleAndLayoutDuration as a combined fallback):
      forcedStyleDuration: longestScript?.entry?.forcedStyleDuration,
      forcedLayoutDuration: longestScript?.entry?.forcedLayoutDuration,
      forcedStyleAndLayoutDuration: longestScript?.entry?.forcedStyleAndLayoutDuration,
      // Rendering-phase style and layout work across the interaction's frames
      // (with totalStyleAndLayoutDuration as a combined fallback):
      renderStyleDuration,
      renderLayoutDuration,
      totalStyleAndLayoutDuration: attribution.totalStyleAndLayoutDuration
    })
  );
});

ベスト プラクティス

  • 推奨事項: 最初に INP のサブパートを使用して、遅延の原因がすでに実行されている JavaScript(入力遅延)、インタラクションのイベント ハンドラ JavaScript(処理時間)、またはその後のレンダリング(表示遅延)のいずれであるかを特定します。
  • 推奨事項: 特に、入力遅延と処理時間について、JavaScript のブロッキングが最も大きいものを追加の詳細として特定します。
  • 推奨事項: 処理時間が長い、または表示遅延が発生している原因を診断する際は、スタイルの再計算(styleDuration、forcedStyleDuration)とレイアウト(layoutDuration、forcedLayoutDuration)を区別します。スタイル時間が長い場合は、CSS セレクタの負荷が高いか、無効化の範囲が広いことを示します。レイアウト時間が長い場合は、DOM ジオメトリのボトルネックまたは同期レイアウトのスラッシングを示します。
  • 推奨: この詳細情報を提供するには、JS Self-Profiling API などの代替手段よりも、Long Animation Frames API を使用することをおすすめします。JS Self-Profiling API は、ランタイム オーバーヘッドが高くなります。
  • 他の RUM ソリューションが導入されていない場合は、web-vitals ライブラリを使用してください。INP インタラクションを特定し、インタラクション中に実行されたサブパートとスクリプトに関する情報(Long Animation Frames API を使用)を含めることができます。
  • 推奨: 必要な情報をローカルに記録するだけでなく、アナリティクス サービスにビーコンバックします。

ブラウザのサポートとフォールバック戦略

Event Timing API は、ほとんどの最新のブラウザで利用可能で、INP と INP のサブパートを計算するために必要です。この API をサポートしていないブラウザでは、INP を測定できません。

Long Animation Frames API には、オプションの追加情報が用意されています。フォールバックなしで安全に使用できます。ほとんどの場合、特定されたパフォーマンス改善の機会は他のブラウザにも適用されます。

ベースライン ターゲットが Long Animation Frames API(long-animation-frames)をサポートしているものの、スタイルとレイアウトの個別の期間プロパティ(PerformanceLongAnimationFrameTiming の styleDuration と layoutDuration、または PerformanceScriptTiming の forcedStyleDuration と forcedLayoutDuration)をサポートしていない場合は、スタイルとレイアウトの組み合わせタイミングにフォールバックします。

  • script.forcedStyleAndLayoutDuration を PerformanceScriptTiming で使用して、スクリプト内で同期的に強制的にスタイルとレイアウトを処理します。
  • スタイルとレイアウトの作業を組み合わせるには、web-vitals/attribution で metric.attribution.totalStyleAndLayoutDuration を使用します(または、各 PerformanceLongAnimationFrameTiming エントリで (entry.paintTime || entry.startTime + entry.duration) - entry.styleAndLayoutStart を計算します)。