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

Long Animation Frames API(LoAF - Lo-Af と発音)は、時間のかかるユーザー インターフェース(UI)の更新について理解を深めるために、Long Tasks API をアップデートしたものです。これは、応答性を測定する Interaction to Next Paint(INP)の Core Web Vitals 指標に影響する可能性の高い遅いアニメーション フレームを特定したり、スムーズさに影響する他の UI ジャンクを特定したりするのに役立ちます。

API のステータス

対応ブラウザ

  • 123
  • x
  • x
  • x

Chrome 116 から Chrome 122 へのオリジン トライアルを経て、Chrome 123 から LoAF API がリリースされました

Long Tasks API

対応ブラウザ

  • 58
  • 79
  • x
  • x

ソース

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

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

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

処理に時間がかかるタスクは応答性の問題を引き起こす可能性があります。ユーザーがページでの操作(ボタンのクリックやメニューの表示など)を試みても、メインスレッドがすでに長いタスクを処理している場合、そのタスクが完了するまでユーザーの操作は遅延します。

応答性を向上させるために、長いタスクを分割することをおすすめします。それぞれの長いタスクを一連の複数の小さなタスクに分割すると、より重要なタスクをそれらの間に実行して、インタラクションへの応答で大幅な遅延を回避できる場合があります。

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

ラボベースのテストは、対応の問題を特定するための出発点として適切でないことがよくあります。これらのツールにはインタラクションが含まれていない場合があり、その場合は、想定されるインタラクションのごく一部にしか含まれない可能性があるためです。現場でのやり取りが遅い原因を測定するのが理想的です。

Long Tasks API の短所

Performance Observer を使用して現場の長時間タスクを測定することは、あまり役に立ちません。実際には、長いタスクが発生したという事実と、それにかかった時間以外にそれほど多くの情報は提供されません。

Real User Monitoring(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

対応ブラウザ

  • 123
  • x
  • x
  • x

Long Animation Frames API(LoAF)は、Long Tasks API の欠点のいくつかに対処することを目的とした新しい API です。デベロッパーは、応答性の問題に対処して INP を改善するために、より実用的な分析情報を取得できるようになります。

応答性の良さとは、ページで行われた操作にページが迅速に反応することを意味します。具体的には、ユーザーが必要とするアップデートをタイムリーにペイントできるようにし、アップデートの実行を妨げないようにします。INP では 200 ミリ秒以内にレスポンスすることが推奨されますが、他のアップデート(アニメーションなど)では長すぎる可能性があります。

Long Animation Frames API は、ブロッキング処理を測定するもう 1 つの方法です。Long Animation Frames API は、その名のとおり、個々のタスクではなく、長いアニメーション フレームを測定します。長いアニメーション フレームとは、レンダリングの更新が 50 ミリ秒(Long Tasks 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 では表示されない可能性がある、前述の最後のポイントに対処できます。

時間のかかるタスクに対するこの代替表示のさらなる利点は、フレーム全体のタイミングの内訳を提供できることです。LoAF には、Long Tasks API のように startTimeduration だけを含めるのではなく、次のようなフレーム時間のさまざまな部分の詳細な内訳が含まれます。

  • startTime: ナビゲーションの開始時間に対する長いアニメーション フレームの開始時間。
  • duration: 長いアニメーション フレームの再生時間(プレゼンテーション時間は含まれません)。
  • renderStart: レンダリング サイクルの開始時間。requestAnimationFrame コールバック、スタイルとレイアウトの計算、サイズ変更オブザーバー、交差点オブザーバーのコールバックが含まれます。
  • styleAndLayoutStart: スタイルとレイアウトの計算に費やされる期間の開始。
  • firstUIEventTimestamp: このフレームで処理される最初の UI イベント(マウス、キーボードなど)の時刻。
  • blockingDuration: アニメーション フレームがブロックされた時間(ミリ秒単位)。

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

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

個々のタイミングについて詳しくは、説明をご覧ください。長いアニメーション フレームに寄与しているアクティビティについて詳しく説明しています。

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

long-animation-frame エントリタイプには、長いアニメーション フレームに影響を与えた各スクリプトの優れたアトリビューション データが含まれています。

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 のパフォーマンス エントリの例

1 つのスクリプトを含む 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 123 以降では、Long Animation Frames API がデフォルトで有効になります。

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

Lighthouse などのツールは問題の発見と再現には便利ですが、フィールド データでしか得られないユーザー エクスペリエンスの重要な側面を見落としてしまうラボツールです。Long Tasks API ではできなかったユーザー操作に関する重要なコンテキスト データを収集するには、このフィールドで Long Animation Frames API を使用します。これにより、他の方法では見つけられなかったようなインタラクティビティの問題を特定し、再現することができます。

以下におすすめの戦略をいくつか紹介しますが、Chrome チームは、この API に関するフィードバックや、API が提供する情報を使用しているデベロッパーや RUM プロバイダについてのフィードバックをお待ちしています。

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

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

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

長いアニメーション フレームがまだデフォルトではサポートされておらず、遷移状態になっている場合は、次のような代替手段を使用できます。

if ('PerformanceLongAnimationFrameTiming' in window) {
  // Monitor LoAFs
}

長いアニメーション データを分析エンドポイントに報告する

次に示すように、LoAF のパフォーマンス エントリには貴重な情報が含まれています。戦略の一つは、すべての LoAF をモニタリングし、特定のしきい値を超える LoAF をビーコンで分析エンドポイントに戻し、後で分析することです。

const REPORTING_THRESHOLD_MS = 150;

const observer = new PerformanceObserver(list => {
  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 });

長いアニメーション フレームのエントリは非常に大きくなる可能性があるため、デベロッパーはエントリからどのデータをアナリティクスに送信するかを決定する必要があります。たとえば、エントリの要約時間やスクリプト名など、必要と思われる最小限のコンテキスト データのセットです。

最長のアニメーション フレームを監視する

ビーコン送信が必要なデータの量を減らすために、最も長いアニメーション フレームでデータを収集したい場合もあります。したがって、ページに長いアニメーション フレームがいくつあっても、最悪の 1 つや 5 つ、あるいは絶対に必要な数の長いアニメーション フレームのデータのみがビーコンで返されます。

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 });

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

console.table(longestBlockingLoAFs);

最長の INP インタラクションへのリンク

最悪の LoAF をモニタリングすることの拡張として、INP エントリに対応する LoAF フレームをアトリビューション データとして使用して、INP の改善方法をさらに詳細に把握できます。

現時点では、INP エントリとそれに関連する LoAF エントリをリンクする直接的な API はありませんが、それぞれの開始時刻と終了時刻を比較することで、コード内でリンクできます(このサンプル スクリプトを参照)。

インタラクションを含む長いアニメーション フレームの報告

必要なコードを減らす別の方法として、フレーム中にインタラクションが発生した最大(または上位 X 個)の LoAF エントリを常に送信することもできます(これは firstUIEventTimestamp 値が存在することで検出できます)。ほとんどの場合、これには特定の訪問の INP インタラクションが含まれますが、含まれていない場合には、他のユーザーにとって INP インタラクションである可能性があるため、修正すべき長いインタラクションが表示されます。

次のコードは、フレーム中にインタラクションが発生した 150 ミリ秒を超えるすべての LoAF エントリをログに記録します。ここでは、200 ミリ秒の「良好」INP しきい値よりもわずかに小さいため、150 を選択します。必要に応じて、より高い値または低い値を選択できます。

const REPORTING_THRESHOLD_MS = 150;

const observer = new PerformanceObserver(list => {
    for (const entry of list.getEntries()) {
      if (entry.duration > 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 });

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

もう 1 つの戦略は、長いアニメーション フレーム エントリで最も出現する一般的なスクリプトを調べることです。違反を繰り返すユーザーを特定するために、スクリプトやキャラクターの位置レベルでデータを報告できます。

これは、パフォーマンスの問題を引き起こしているテーマやプラグインを複数のサイトでより簡単に特定できる、カスタマイズ可能なプラットフォームに特に効果的です。

長いアニメーション フレームにおける一般的なスクリプトまたはサードパーティ オリジンの実行時間を合計してレポートし、1 つのサイトまたは一連のサイトにおける長いアニメーション フレームの一般的な原因を特定できます。たとえば、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 で長いアニメーション フレームデータを表示する

DevTools で performance.measure() API を使用して長いアニメーション フレームを表示できます。このフレームは、DevTools カスタム速度トラックのパフォーマンス トレースに表示され、パフォーマンス改善に注力すべき領域を示します。

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    performance.measure('LoAF', {
      start: entry.startTime,
      end: entry.startTime + entry.duration,
    });
  }
});

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

この API が長期的に役に立つことがわかったら、おそらく DevTools 自体に組み込まれるでしょう。ただし、以前のコード スニペットを使用すれば、当面はそれを目立たせることができます。

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

Web Vitals 拡張機能が、パフォーマンスの問題を診断できるように、ロギングのサマリー デバッグ情報の値を表示しました。このたび、この API がリリースされました。このようなツールは、開発者がどこで労力を注ぐべきかを認識できるよう、データを簡単に表示できるようになります。今後、バージョン 4 のウェブに関する指標の JavaScript ライブラリにも追加される予定です。

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

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

よくある質問

この API に関するよくある質問には、次のようなものがあります。

Long Tasks API を拡張またはイテレーションするだけでよいのでしょうか。

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

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

これは Long Tasks API に置き換わるものですか?

Google では、長時間のタスクを計測するには Long Animation Frames API の方が優れており、より完全な API であると考えていますが、現時点では Long Tasks API を廃止する予定はありません。

フィードバックのお願い

フィードバックは、GitHub の [Issues] リストからお送りいただけます。また、Chrome の API 実装に関するバグについては、Chrome の Issue Tracker でお知らせください。

おわりに

Long Animation Frames API は、以前の Long Tasks API よりも多くのメリットを得られる新しい API です。

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

ただし、Long Animation Frames API の範囲は INP にとどまらず、ウェブサイトのユーザー エクスペリエンスの全体的なスムーズさに影響を与える可能性のある更新が遅い他の原因を特定するのに役立ちます。

謝辞

サムネイル画像(作成者: Henry Be、出典: Unsplash