長いアニメーション フレーム API

Long Animation Frames API(LoAF)は、ユーザー インターフェース(UI)の更新の遅延をより正確に把握できるように Long Tasks API を更新したものです。これは、応答性を測定する Core Web Vitals 指標である Interaction to Next Paint(INP)に影響する可能性のある遅いアニメーション フレームや、スムーズさに影響するその他の UI ジャンクを特定するのに役立ちます。

API のステータス

対応ブラウザ

  • Chrome: 123。
  • Edge: 123。
  • Firefox: サポートされていません。
  • Safari: サポートされていません。

ソース

Chrome 116 ~ Chrome 122 のオリジン トライアルに続いて、LoAF API は Chrome 123 からリリースされました。

背景: Long Tasks API

対応ブラウザ

  • Chrome: 58.
  • Edge: 79.
  • Firefox: サポートされていません。
  • Safari: サポートされていません。

ソース

Long Animation Frames API は、Chrome で長い間(Chrome 58 以降)利用可能な Long Tasks API の代替手段です。名前が示すように、Long Task API を使用すると、長時間のタスク(メインスレッドを 50 ミリ秒以上占有するタスク)をモニタリングできます。長時間のタスクは、PeformanceObserver を使用して PerformanceLongTaskTiming インターフェースでモニタリングできます。

const observer = new PerformanceObserver((list) => {
  console.log(list.getEntries());
});

observer.observe({ type: 'longtask', buffered: true });

タスクが長すぎると、応答性に問題が生じる可能性があります。ユーザーがページの操作(ボタンのクリックやメニューの開くなど)を試みても、メインスレッドがすでに長いタスクを処理している場合、そのタスクが完了するまでユーザーの操作が遅延します。

応答性を高めるには、長いタスクを分割することをおすすめします。長いタスクを複数の小さなタスクに分割すると、タスクの合間により重要なタスクを実行できるため、インタラクションへの応答が大幅に遅延するのを防ぐことができます。

そのため、応答性を改善しようとする際、多くの場合、最初の作業はパフォーマンス トレースを実行して長時間のタスクを確認することです。たとえば、Lighthouse などのラボベースの監査ツール(長いメインスレッド タスクを回避する監査があります)を使用するか、Chrome DevTools で長いタスクを確認します。

ラボベースのテストは、レスポンシビリティの問題を特定する際には適切でないことが多いため、これらのツールにインタラクションが含まれていない場合があります。含まれている場合でも、想定されるインタラクションの小さなサブセットです。理想的には、フィールドでインタラクションの遅延の原因を測定します。

Long Tasks API の欠点

フィールドで長いタスクを測定する場合、Performance Observer は多少役に立ちます。実際には、長いタスクが発生したという事実と、その時間以外に多くの情報は得られません。

リアルユーザー モニタリング(RUM)ツールでは、この情報を使用して長時間のタスクの数や所要時間の傾向を把握したり、長時間のタスクが発生したページを特定したりすることがよくあります。しかし、長時間のタスクの原因に関する詳細情報がないと、この情報は限定的な用途しかありません。Long Tasks API には基本的なアトリビューション モデルのみが用意されています。このモデルでは、長時間タスクが発生したコンテナ(最上位ドキュメントまたは <iframe>)のみが判明し、呼び出したスクリプトや関数は判明しません。これは、次の典型的なエントリで示されています。

{
  "name": "unknown",
  "entryType": "longtask",
  "startTime": 31.799999997019768,
  "duration": 136,
  "attribution": [
    {
      "name": "unknown",
      "entryType": "taskattribution",
      "startTime": 0,
      "duration": 0,
      "containerType": "window",
      "containerSrc": "",
      "containerId": "",
      "containerName": ""
    }
  ]
}

Long Tasks API は、一部の重要なタスクが除外される可能性があるため、不完全なビューでもあります。レンダリングなどの更新は、別々のタスクで行われますが、理想的には、その更新を引き起こした前の実行とともに、そのインタラクションの「合計作業量」を正確に測定するために、その更新を含める必要があります。タスクに依存する制限事項の詳細については、説明の「長いタスクの限界」セクションをご覧ください。

最後の問題は、長いタスクの測定では、50 ミリ秒の制限を超える個々のタスクのみがレポートされることです。アニメーション フレームは、この 50 ミリ秒の制限よりも小さい複数のタスクで構成されている場合がありますが、それでもブラウザのレンダリング能力をブロックする可能性があります。

Long Animation Frames API

対応ブラウザ

  • Chrome: 123。
  • Edge: 123。
  • Firefox: サポートされていません。
  • Safari: サポートされていません。

ソース

Long Animation Frames API(LoAF)は、Long Tasks API の欠点の一部に対処することを目的とした新しい API です。これにより、デベロッパーは、応答性の問題に対処し、INP を改善するために、より実用的な分析情報を入手できるようになります。また、スムーズさに関する問題に関する分析情報も入手できます。

応答性が良好とは、ページが操作に対して迅速に応答することを意味します。つまり、ユーザーが必要とする更新をタイムリーにペイントし、これらの更新がブロックされないようにする必要があります。INP の場合は200 ミリ秒以内にレスポンスを返すことをおすすめしますが、他の更新(アニメーションなど)の場合は 200 ミリ秒でも長すぎる場合があります。

Long Animation Frames API は、ブロック作業を測定する代替手段です。Long Animation Frames API は、その名のとおり、個々のタスクではなく長いアニメーション フレームを測定します。長いアニメーション フレームとは、レンダリングの更新が 50 ミリ秒(Long Tasks API のしきい値と同じ)を超えて遅延した場合です。

長いアニメーション フレームは、レンダリングを必要とするタスクの開始から測定されます。長いアニメーション フレームの最初のタスクでレンダリングが不要な場合、レンダリング以外のタスクの完了時に長いアニメーション フレームが終了し、次のタスクで新しい長いアニメーション フレームが開始されます。このようなレンダリングされない長いアニメーション フレームは、50 ミリ秒を超える場合(renderStart 時間が 0 の場合)は、ブロックする可能性のある作業を測定できるように、Long Animation Frames API に引き続き含まれます。

長いアニメーション フレームは、PerformanceObserver を使用した長いタスクと同様に確認できますが、代わりに long-animation-frame タイプを確認します。

const observer = new PerformanceObserver((list) => {
  console.log(list.getEntries());
});

observer.observe({ type: 'long-animation-frame', buffered: true });

以前の長いアニメーション フレームは、パフォーマンス タイムラインから次のようにクエリすることもできます。

const loafs = performance.getEntriesByType('long-animation-frame');

ただし、パフォーマンス エントリの maxBufferSize があり、それ以降のエントリは破棄されるため、PerformanceObserver アプローチをおすすめします。long-animation-frame バッファサイズは、long-tasks と同じ 200 に設定されています。

タスクではなくフレームを確認するメリット

タスクの観点ではなくフレームの観点からこれを検討する主な利点は、長いアニメーションを任意の数のタスクで構成し、累積的に長いアニメーション フレームを作成できることです。これは、前述の最後のポイントに対応しています。アニメーション フレームの前に、レンダリングをブロックする小さなタスクが多数あり、その合計が Long Tasks API によってサーフェス表示されなくなる可能性があります。

長いタスクに対するこの代替ビューのもう 1 つの利点は、フレーム全体のタイミングの内訳を提供できることです。Long Tasks API のように startTimeduration のみを指定せず、LoAF ではフレーム時間のさまざまな部分をより詳細に分類します。

フレームのタイムスタンプと時間

  • startTime: ナビゲーションの開始時間を基準とした長いアニメーション フレームの開始時間。
  • duration: 長いアニメーション フレームの時間(表示時間は含まない)。
  • renderStart: レンダリング サイクルの開始時間(requestAnimationFrame コールバック、スタイルとレイアウトの計算、サイズ変更オブザーバー、インターセクション オブザーバーのコールバックを含む)。
  • styleAndLayoutStart: スタイルとレイアウトの計算に費やされた期間の開始時間。
  • firstUIEventTimestamp: このフレーム中に処理される最初の UI イベント(マウス/キーボードなど)の時間。
  • blockingDuration: アニメーション フレームが入力やその他の優先度の高いタスクの処理をブロックする合計時間(ミリ秒単位)。

blockingDuration の説明

長いアニメーション フレームは、複数のタスクで構成されている場合があります。blockingDuration は、50 ミリ秒を超えるタスクの実行時間の合計(最長タスク内の最終レンダリング時間を含む)です。

たとえば、長いアニメーション フレームが 55 ミリ秒と 65 ミリ秒の 2 つのタスクで構成され、その後に 20 ミリ秒のレンダリングが続く場合、duration は約 140 ミリ秒で、blockingDuration は(55 - 50)+(65 + 20 - 50)= 40 ミリ秒になります。この 140 ミリ秒のアニメーション フレームのうち 40 ミリ秒は、入力の処理がブロックされたフレームと見なされます。

durationblockingDuration のどちらを確認するか

一般的な 60 Hz ディスプレイの場合、ブラウザは(スムーズな更新を確保するために)少なくとも 16.66 ミリ秒ごとに、または(レスポンシブな更新を確保するために)入力処理などの優先度の高いタスクの後に、フレームのスケジュールを設定しようとします。ただし、入力も他の優先度の高いタスクもないが、他のタスクのキューがある場合、通常、ブラウザは、タスクがどれだけ細かく分割されているかにかかわらず、現在のフレームを 16.66 ミリ秒をはるかに超えて継続します。つまり、ブラウザは常に入力を優先しようとしますが、レンダリングの更新よりもタスクのキューに対応することを選択する場合があります。これは、レンダリングが負荷の高いプロセスであるため、複数のタスクの組み合わせによるレンダリング タスクを処理すると、通常は全体的な作業量が削減されるためです。

したがって、blockingDuration が低いまたはゼロの長いアニメーション フレームでも、入力に応答しているように感じられる必要があります。したがって、長いタスクを分割して blockingDuration を減らすか、完全になくすことが、INP で測定される応答性を改善する鍵となります。

ただし、blockingDuration に関係なく、長いアニメーション フレームが多数ある場合は、UI の更新が遅れていることを示します。そのため、INP で測定される応答性の問題は少ないものの、スムーズさに影響し、スクロールやアニメーションのユーザー インターフェースが遅いと感じられる可能性があります。この領域の問題を把握するには、duration を確認します。ただし、作業を分割して解決することはできず、作業を減らす必要があるため、最適化が難しい場合があります。

フレームタイミング

前述のタイムスタンプを使用すると、長いアニメーション フレームを次のようにタイミングに分割できます。

タイミング 計算
開始時間 startTime
終了時間 startTime + duration
勤務時間 renderStart ? renderStart - startTime : duration
レンダリング所要時間 renderStart ? (startTime + duration) - renderStart: 0
レンダリング: レイアウト前の時間 styleAndLayoutStart ? styleAndLayoutStart - renderStart : 0
レンダリング: スタイルとレイアウトの時間 styleAndLayoutStart ? (startTime + duration) - styleAndLayoutStart : 0

スクリプト アトリビューションの改善

long-animation-frame エントリタイプには、長いアニメーション フレームに貢献した各スクリプトのアトリビューション データが含まれています(5 ミリ秒を超えるスクリプトの場合)。

Long Tasks API と同様に、これはアトリビューション エントリの配列で提供されます。各エントリには次の詳細が含まれます。

  • nameEntryType の両方が script を返します。
  • スクリプトの呼び出し方法を示す意味のある invoker'IMG#id.onload''Window.requestAnimationFrame''Response.json.then' など)。
  • スクリプト エントリ ポイントの invokerType:
    • user-callback: ウェブ プラットフォーム API から登録された既知のコールバック(setTimeoutrequestAnimationFrame など)。
    • event-listener: プラットフォーム イベントのリスナー(clickloadkeyup など)。
    • resolve-promise: プラットフォームの Promise のハンドラ(例: fetch())。なお、Promise の場合、同じ Promise のハンドラはすべて 1 つの「スクリプト」として混在します。.
    • reject-promise: resolve-promise と同じですが、不承認の場合。
    • classic-script: スクリプトの評価(例: <script>import()
    • module-script: classic-script と同じですが、モジュール スクリプト用です。
  • そのスクリプトの個別のタイミング データ:
    • startTime: エントリ関数が呼び出された時刻。
    • duration: startTime から、後続のマイクロタスク キューの処理が完了するまでの時間。
    • executionStart: コンパイル後の時間。
    • forcedStyleAndLayoutDuration: この関数内で強制レイアウトとスタイルの処理に費やされた合計時間(スラッシングを参照)。
    • pauseDuration: 同期オペレーションの「一時停止」に費やされた合計時間(アラート、同期 XHR)。
  • スクリプト ソースの詳細:
    • sourceURL: 利用可能な場合のスクリプト リソース名(見つからない場合は空)。
    • sourceFunctionName: 使用可能なスクリプト関数名(見つからない場合は空)。
    • sourceCharPosition: スクリプト文字の位置(見つからない場合は -1)。
  • windowAttribution: 長いアニメーション フレームが発生したコンテナ(最上位ドキュメントまたは <iframe>)。
  • window: 同じオリジンのウィンドウへの参照。

ソースエントリが指定されている場合、デベロッパーは長いアニメーション フレーム内の各スクリプトがどのように呼び出されたか、呼び出し元のスクリプトの文字位置まで正確に把握できます。これにより、長いアニメーション フレームの原因となった JavaScript リソース内の正確な場所が示されます。

long-animation-frame パフォーマンス エントリの例

単一のスクリプトを含む long-animation-frame パフォーマンス エントリの例を以下に示します。

{
  "blockingDuration": 0,
  "duration": 60,
  "entryType": "long-animation-frame",
  "firstUIEventTimestamp": 11801.099999999627,
  "name": "long-animation-frame",
  "renderStart": 11858.800000000745,
  "scripts": [
    {
      "duration": 45,
      "entryType": "script",
      "executionStart": 11803.199999999255,
      "forcedStyleAndLayoutDuration": 0,
      "invoker": "DOMWindow.onclick",
      "invokerType": "event-listener",
      "name": "script",
      "pauseDuration": 0,
      "sourceURL": "https://web.dev/js/index-ffde4443.js",
      "sourceFunctionName": "myClickHandler",
      "sourceCharPosition": 17796,
      "startTime": 11803.199999999255,
      "window": [Window object],
      "windowAttribution": "self"
    }
  ],
  "startTime": 11802.400000000373,
  "styleAndLayoutStart": 11858.800000000745
}

このように、ウェブサイトはこれまで以上に多くのデータを取得して、レンダリングの更新の遅延の原因を把握できるようになります。

フィールドで Long Animation Frames API を使用する

Chrome DevTools や Lighthouse などのツールは、問題の検出と再現に役立ちますが、ラボツールであるため、フィールド データでしか得られないユーザー エクスペリエンスの重要な側面を見落とす可能性があります。

Long Animation Frames API は、Long Tasks API では収集できない、ユーザー操作に関する重要なコンテキスト データをフィールドで収集するように設計されています。これにより、他の方法では発見できなかったインタラクティビティに関する問題を特定して再現できます。

長いアニメーション フレーム API のサポート機能の検出

次のコードを使用して、API がサポートされているかどうかをテストできます。

if (PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')) {
  // Monitor LoAFs
}

Long Animation Frames API の最も明らかなユースケースは、次のペイントへのインタラクション(INP)の問題の診断と修正に役立つことです。これは、Chrome チームがこの API を開発した主な理由の一つでした。良好な INP とは、すべてのインタラクションに対して、インタラクションからフレームがペイントされるまでの 200 ミリ秒以内にレスポンスが返される INP です。Long Animation Frames API は 50 ミリ秒以上の時間を要するすべてのフレームを測定するため、問題のある INP のほとんどには、インタラクションの診断に役立つ LoAF データを含める必要があります。

次の図に示すように、「INP LoAF」は INP インタラクションを含む LoAF です。

ページ上の長いアニメーション フレームの例。INP LoAF がハイライト表示されています。
1 つのページに複数の LoAF が存在し、そのうちの 1 つが INP インタラクションに関連している場合があります。

場合によっては、INP イベントが 2 つの LoAF にまたがることがあります。通常は、フレームが前フレームのレンダリング部分を開始した後にインタラクションが発生し、イベント ハンドラが次のフレームで処理される場合に発生します。

ページ上の長いアニメーション フレームの例。INP LoAF がハイライト表示されています。
1 つのページに複数の LoAF が存在し、そのうちの 1 つが INP インタラクションに関連している場合があります。

まれに、2 つを超える LoAF にまたがることもあります。

INP インタラクションに関連付けられた LoAF データを記録すると、INP インタラクションに関する詳細な情報を取得して診断に役立てることができます。これは、入力遅延を把握する際に特に役立ちます。そのフレームで実行されていた他のスクリプトを確認できます。

また、イベント ハンドラでこれらの値が再現されない場合は、説明できない処理時間表示遅延を把握することも役に立ちます。ユーザーに対して、ご自身のテストに含まれていない他のスクリプトが実行されている可能性があります。

INP エントリを関連する LoAF エントリにリンクする直接の API はありませんが、各エントリの開始時間と終了時間を比較することで、コード内でリンクすることは可能です(WhyNp サンプル スクリプトをご覧ください)。web-vitals ライブラリには、v4 の INP アトリビューション インターフェースの longAnimationFramesEntries プロパティに含まれる、重複するすべての LoAF が含まれています。

LoAF エントリをリンクしたら、INP アトリビューションを含む情報を含めることができます。scripts オブジェクトには、そのフレームで実行されていた他の処理も示されるため、非常に有用な情報が含まれています。このデータをアナリティクス サービスにビーコンで送信すると、インタラクションの遅延の原因を詳しく把握できます。

INP インタラクションの LoAF をレポートすると、ページで最も緊急性の高いインタラクションの問題を特定できます。ユーザーによってページの操作は異なるため、十分な量の INP アトリビューション データが収集されると、INP アトリビューション データにさまざまな問題が含まれる可能性があります。これにより、スクリプトをボリューム別に並べ替えて、INP の遅延と関連するスクリプトを確認できます。

長いアニメーション データをアナリティクス エンドポイントに報告する

INP LoAF のみを確認するデメリットの一つは、将来の INP の問題の原因となる可能性のある他の改善領域を見逃す可能性があることです。そのため、INP の問題を修正して大幅な改善を期待しても、次に遅いインタラクションの時間がわずかに短縮されただけで、INP の改善があまり進まない、という状況に陥る可能性があります。

そのため、INP LoAF のみを見るのではなく、ページのライフタイム全体のすべての LoAF を検討することをおすすめします。

LoAF が多いページ。一部の LoAF は、INP インタラクション以外でもインタラクション中に発生します。
すべての LoAF を確認すると、将来の INP の問題を特定できます。

ただし、各 LoAF エントリには大量のデータが含まれているため、分析対象を一部のロアフのみに限定することをおすすめします。また、長いアニメーション フレームのエントリは非常に大きい場合があるため、エントリから分析に送信するデータをデベロッパーが決定する必要があります。たとえば、エントリの概要時間やスクリプト名、または必要と思われるその他のコンテキスト データの最小セットなどです。

長いアニメーション フレーム データの量を減らすための推奨されるパターンには、次のようなものがあります。

これらのパターンのどれが最適かは、最適化の進捗状況と、長いアニメーション フレームの頻度によって異なります。レスポンシブ デザインを最適化したことがないサイトでは、LoAF が多数存在する可能性があります。そのような場合は、インタラクションが発生した LoAF のみに絞り込むか、高いしきい値を設定する、または最も問題のある LoAF のみを確認することをおすすめします。

一般的な応答性の問題を解決したら、インタラクションや長時間のブロックのみに限定せず、しきい値を下げて範囲を広げることができます。

インタラクションのある長いアニメーション フレームをモニタリングする

INP の長いアニメーション フレーム以外の分析情報を得るには、blockingDuration が高いインタラクション(firstUIEventTimestamp 値の有無で検出可能)を含むすべての LoAF を確認します。

また、2 つの関連付けを試みるよりも、INP LoAF をモニタリングする簡単な方法でもあります。ほとんどの場合、特定のアクセスの INP LoAF が含まれますが、含まれない場合でも、他のユーザーの INP インタラクションである可能性があるため、修正が必要な長いインタラクションが表示されます。

次のコードは、フレーム中にインタラクションが発生したすべての LoAF エントリを、blockingDuration が 100 ミリ秒を超える場合にロギングします。ここで 100 が選択されているのは、200 ミリ秒の「良好な」INP しきい値よりも小さいためです。必要に応じて、この値を大きくしたり小さくしたりできます。

const REPORTING_THRESHOLD_MS = 100;

const observer = new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    if (entry.blockingDuration > REPORTING_THRESHOLD_MS &&
      entry.firstUIEventTimestamp > 0
    ) {
      // Example here logs to console, but could also report back to analytics
      console.log(entry);
    }
  }
});
observer.observe({ type: 'long-animation-frame', buffered: true });

長いアニメーション フレームと長いブロック時間を確認する

インタラクションのある長いアニメーション フレームすべてを確認する代わりに、ブロック時間が長い長いアニメーション フレームすべてを確認することをおすすめします。これらの長いアニメーション フレーム中にユーザーが操作した場合、潜在的な INP の問題が発生する可能性があります。

次のコードは、フレーム中にインタラクションが発生した、ブロック時間 100 ミリ秒を超えるすべての LoAF エントリをロギングします。ここで 100 が選択されているのは、200 ミリ秒の「正常な」INP しきい値よりも小さいため、問題の可能性があるフレームを特定しつつ、報告される長いアニメーション フレームの量を最小限に抑えることができるためです。必要に応じて、この値を大きくしたり小さくしたりできます。

const REPORTING_THRESHOLD_MS = 100;

const observer = new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    if (entry.blockingDuration > REPORTING_THRESHOLD_MS) {
      // Example here logs to console, but could also report back to analytics
      console.log(entry);
    }
  }
});
observer.observe({ type: 'long-animation-frame', buffered: true });

重要な UI の更新中に長いアニメーション フレームを監視してスムーズさを改善

前述のように、長いアニメーション フレームのブロック時間が長い場合は、入力の応答性に問題がある可能性があります。ただし、スムーズにするには、長い duration で長いアニメーション フレームを確認する必要があります。

ノイズが非常に多い場合は、次のようなパターンで測定ポイントをキーポイントに制限することをおすすめします。

const REPORTING_THRESHOLD_MS = 100;

const observer = new PerformanceObserver(list => {
  if (measureImportantUIupdate) {
    for (const entry of list.getEntries()) {
      if (entry.duration > REPORTING_THRESHOLD_MS) {
        // Example here logs to console, but could also report back to analytics
        console.log(entry);
      }
    }
  }
});
observer.observe({ type: 'long-animation-frame', buffered: true });

async function doUIUpdatesWithMeasurements() {
  measureImportantUIupdate = true;
  await doUIUpdates();
  measureImportantUIupdate = false;
}

最も長いアニメーション フレームを観察する

サイトによっては、ビーコンで送信する必要があるデータ量を減らすために、しきい値を設定するのではなく、最も長いアニメーション フレーム(またはフレーム)に関するデータを収集することもできます。そのため、ページで長いアニメーション フレームがいくつ発生しても、最も長いアニメーション フレーム(5 フレーム、10 フレームなど、絶対に必要なフレーム数)のデータのみがビーコンで送信されます。

MAX_LOAFS_TO_CONSIDER = 10;
let longestBlockingLoAFs = [];

const observer = new PerformanceObserver(list => {
  longestBlockingLoAFs = longestBlockingLoAFs.concat(list.getEntries()).sort(
    (a, b) => b.blockingDuration - a.blockingDuration
  ).slice(0, MAX_LOAFS_TO_CONSIDER);
});
observer.observe({ type: 'long-animation-frame', buffered: true });

これらの戦略を組み合わせることもできます。たとえば、インタラクション時間が 100 ミリ秒を超える LoAF のうち、最もパフォーマンスの悪い 10 個のみを確認します。

適切なタイミング(visibilitychange イベントが理想的)で、アナリティクスにビーコンを返します。ローカルテストでは、console.table を定期的に使用できます。

console.table(longestBlockingLoAFs);

長いアニメーション フレームの一般的なパターンを特定する

別の方法として、長いアニメーション フレーム エントリで最も多く出現する一般的なスクリプトを確認することもできます。データはスクリプトと文字位置レベルで報告され、違反を繰り返すユーザーを特定できます。

これは、カスタマイズ可能なプラットフォームで、パフォーマンスの問題の原因となっているテーマやプラグインを複数のサイトで特定できる場合に特に効果的です。

長いアニメーション フレーム内の一般的なスクリプト(またはサードパーティ オリジン)の実行時間を合計してレポートすることで、サイトまたはサイトのコレクション全体で長いアニメーション フレームの発生に共通して影響している要素を特定できます。たとえば、URL を確認するには:

const observer = new PerformanceObserver(list => {
  const allScripts = list.getEntries().flatMap(entry => entry.scripts);
  const scriptSource = [...new Set(allScripts.map(script => script.sourceURL))];
  const scriptsBySource= scriptSource.map(sourceURL => ([sourceURL,
      allScripts.filter(script => script.sourceURL === sourceURL)
  ]));
  const processedScripts = scriptsBySource.map(([sourceURL, scripts]) => ({
    sourceURL,
    count: scripts.length,
    totalDuration: scripts.reduce((subtotal, script) => subtotal + script.duration, 0)
  }));
  processedScripts.sort((a, b) => b.totalDuration - a.totalDuration);
  // Example here logs to console, but could also report back to analytics
  console.table(processedScripts);
});

observer.observe({type: 'long-animation-frame', buffered: true});

出力の例を次に示します。

(index) sourceURL count totalDuration
0 'https://example.consent.com/consent.js' 1 840
1 'https://example.com/js/analytics.js' 7 628
2 'https://example.chatapp.com/web-chat.js' 1 5

ツールで Long Animation Frames API を使用する

この API を使用すると、ローカル デバッグ用の追加のデベロッパー ツールも使用できます。Lighthouse や Chrome DevTools などの一部のツールは、低レベルのトレース詳細を使用してこのデータの大部分を収集できましたが、この高レベルの API を使用すると、他のツールもこのデータにアクセスできるようになります。

DevTools で長いアニメーション フレームのデータを確認する

performance.measure() API を使用して、長いアニメーション フレームを DevTools に表示できます。このフレームは、パフォーマンス トレースの DevTools のユーザー タイミング トラックに表示され、パフォーマンス改善に重点を置くべき場所を示します。DevTools Extensibility API を使用すると、これらのイベントを独自のトラックに表示することもできます。

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    performance.measure('LoAF', {
      start: entry.startTime,
      end: entry.startTime + entry.duration,
      detail: {
        devtools: {
          dataType: "track-entry",
          track: "Long animation frames",
          trackGroup: "Performance Timeline",
          color: "tertiary-dark",
          tooltipText: 'LoAF'
        }
      }
    });
  }
});

observer.observe({ type: 'long-animation-frame', buffered: true });
メインの炎グラフと比較できる長いアニメーション フレームのデータが表示されたカスタム トラックを含む DevTools パフォーマンス パネルのトレース。
DevTools で長いアニメーション フレームデータを表示。

長期的には、長いアニメーション フレームは DevTools 自体に組み込まれる可能性がありますが、それまでの間は、前のコード スニペットを使用して表示できます。

上の図の最初のエントリは、ブラウザが複数のタスクを、タスクの間にレンダリングするのではなく、同じ長いアニメーション フレームでまとめて処理した場所を示しています。前述のように、優先度の高い入力タスクは存在しないものの、タスクのキューがある場合に発生することがあります。最初の長いタスクには、完了するレンダリング アップデートがいくつかあります(完了しないと、現在の長いアニメーション フレームはその後でリセットされ、次のタスクで新しいフレームが開始されます)。しかし、ブラウザは、そのレンダリングをすぐに実行するのではなく、いくつかの追加タスクを処理してから、長いレンダリング タスクを実行し、長いアニメーション フレームを終了しました。これは、長いタスクだけでなく、DevTools で長いアニメーション フレームを確認して、レンダリングの遅延を特定する方法の有用性を示しています。

他のデベロッパー ツールで長いアニメーション フレームのデータを使用する

ウェブに関する主な指標拡張機能が、ロギング概要のデバッグ情報に値を表示して、パフォーマンスの問題を診断できるようになりました。

また、各 INP コールバックと各インタラクションの長いアニメーション フレームデータも表示されるようになります。

Web Vitals 拡張機能のコンソール ロギング。
ウェブに関する指標拡張機能のコンソール ロギングに LoAF データが表示される。

自動テストツールで長いアニメーション フレームデータを使用する

同様に、CI/CD パイプラインの自動テストツールは、さまざまなテストスイートを実行しながら長いアニメーション フレームを測定することで、潜在的なパフォーマンスの問題の詳細を表示できます。

よくある質問

この API に関するよくある質問を以下に示します。

Long Tasks API を拡張または反復処理しないのはなぜですか?

これは、潜在的な応答性の問題に関する同様の測定値(最終的には異なる測定値)を報告する別の方法です。既存の Long Tasks API に依存するサイトが引き続き機能するようにし、既存のユースケースが中断されないようにすることが重要です。

Long Tasks API は、LoAF の一部機能(アトリビューション モデルの改善など)を活用できますが、タスクではなくフレームに焦点を当てることで、既存の Long Tasks API とは根本的に異なる API となり、多くのメリットが得られると考えられます。

スクリプト エントリがないのはなぜですか?

これは、長いアニメーション フレームが JavaScipt ではなく、レンダリング作業の負荷が高いことが原因であることを示している可能性があります。

また、長いアニメーション フレームが JavaScript によるものであるにもかかわらず、前述のさまざまなプライバシー上の理由(主に、JavaScript がページ所有のものではない)によりスクリプトの帰属を提供できない場合にも発生することがあります。

スクリプト エントリはあるのに、ソース情報がないか、制限されているのはなぜですか?

これは、適切な参照元がないなど、さまざまな理由で発生する可能性があります。

スクリプト情報も、no-cors cross-origin スクリプトの場合は sourceURL(リダイレクトを除く)のみに制限され、sourceFunctionName は空の文字列、sourceCharPosition-1 になります。この問題は、<script> 呼び出しに crossOrigin = "anonymous" を追加して CORS を使用してスクリプトを取得することで解決できます。

たとえば、ページに追加するデフォルトの Google タグ マネージャー スクリプトは次のとおりです。

<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX');</script>
<!-- End Google Tag Manager -->

j.crossOrigin = "anonymous" を追加して拡張すると、GTM に完全なアトリビューションの詳細を提供できます。

これは Long Tasks API に代わるものですか?

Long Animation Frames API は、長いタスクの測定に適した、より優れた API ですが、現時点では Long Tasks API を非推奨にする予定はありません。

フィードバックを求めている

フィードバックは GitHub の問題リストで送信できます。また、Chrome で実装された API のバグは Chrome の問題トラッカーで報告できます。

まとめ

Long Animation Frames API は、以前の Long Tasks API よりも多くのメリットが期待される、魅力的な新しい API です。

INP で測定される応答性の問題に対処するための重要なツールであることが実証されています。INP は最適化が難しい指標です。この API は、デベロッパーが問題を簡単に特定して対処できるように、Chrome チームが検討している方法の一つです。

ただし、Long Animation Frames API の範囲は INP だけに限定されません。ウェブサイトのユーザー エクスペリエンスの全体的なスムーズさに影響する可能性のある、更新の遅延の他の原因を特定するのにも役立ちます。

謝辞

サムネイル画像: UnsplashHenry Be による