ウェブサイトのフィールド データで遅いインタラクションを見つけて、Interaction to Next Paint を改善する機会を見つける方法について説明します。
フィールド データは、実際のユーザーがウェブサイトをどのように利用しているかを示すデータです。ラボデータだけでは見つからない問題を特定できます。Interaction to Next Paint(INP)に関しては、フィールド データは、遅いインタラクションを特定するために不可欠であり、それらを修正するのに役立つ重要な手がかりを提供します。
このガイドでは、Chrome ユーザー エクスペリエンス レポート(CrUX)のフィールド データを使用して、ウェブサイトの INP をすばやく評価し、ウェブサイトに INP の問題があるかどうかを確認する方法について説明します。その後、web-vitals JavaScript ライブラリのアトリビューション ビルドと、Long Animation Frames API(LoAF)から得られる新しい分析情報を使用して、ウェブサイトでの遅いインタラクションのフィールド データを収集して解釈する方法を学びます。
CrUX を使用してウェブサイトの INP を評価する
ウェブサイトのユーザーからフィールド データを収集していない場合は、CrUX から始めることをおすすめします。CrUX は、テレメトリー データの送信をオプトインした実際の Chrome ユーザーからフィールド データを収集します。
CrUX データはさまざまな領域で表示されます。どの領域に表示されるかは、探している情報の範囲によって異なります。CrUX は、INP やその他の Core Web Vitals に関するデータを次の対象について提供できます。
- PageSpeed Insights を使用した個々のページとオリジン全体。
- ページの種類。たとえば、多くの e コマース ウェブサイトには、商品の詳細ページと商品リスティング ページの種類があります。Search Console で、一意のページタイプの CrUX データを取得できます。
まずは、PageSpeed Insights にウェブサイトの URL を入力してみましょう。URL を入力すると、INP を含む複数の指標のフィールド データ(利用可能な場合)が表示されます。切り替えボタンを使用して、モバイルとパソコンのディメンションの INP 値を確認することもできます。
このデータは、問題があるかどうかを判断するのに役立ちます。ただし、CrUX では問題の原因を特定することはできません。この疑問に答えるために、ウェブサイトのユーザーから独自のフィールド データを収集するのに役立つ Real User Monitoring(RUM)ソリューションが多数用意されています。その 1 つとして、web-vitals JavaScript ライブラリを使用してフィールド データを自分で収集する方法があります。
web-vitals JavaScript ライブラリを使用してフィールド データを収集する
web-vitals JavaScript ライブラリは、ウェブサイトに読み込んで、ウェブサイトのユーザーからフィールド データを収集できるスクリプトです。これを使用すると、INP をサポートするブラウザで INP を含むさまざまな指標を記録できます。
web-vitals ライブラリの標準ビルドを使用すると、フィールドのユーザーから基本的な INP データを取得できます。
import {onINP} from 'web-vitals';
onINP(({name, value, rating}) => {
console.log(name); // 'INP'
console.log(value); // 512
console.log(rating); // 'poor'
});
ユーザーから収集したフィールド データを分析するには、このデータをどこかに送信する必要があります。
import {onINP} from 'web-vitals';
onINP(({name, value, rating}) => {
// Prepare JSON to be sent for collection. Note that
// you can add anything else you'd want to collect here:
const body = JSON.stringify({name, value, rating});
// Use `sendBeacon` to send data to an analytics endpoint.
// For Google Analytics, see https://github.com/GoogleChrome/web-vitals#send-the-results-to-google-analytics.
navigator.sendBeacon('/analytics', body);
});
ただし、このデータだけでは、CrUX 以上の情報は得られません。そこで、web-vitals ライブラリのアトリビューション ビルドの出番です。
web-vitals ライブラリのアトリビューション ビルドをさらに活用する
web-vitals ライブラリのアトリビューション ビルドでは、フィールドのユーザーから取得できる追加のデータが表示されるため、ウェブサイトの INP に影響する問題のあるインタラクションのトラブルシューティングに役立ちます。このデータには、ライブラリの onINP() メソッドで表示される attribution オブジェクトを介してアクセスできます。
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, rating, attribution}) => {
console.log(name); // 'INP'
console.log(value); // 56
console.log(rating); // 'good'
console.log(attribution); // Attribution data object
});
アトリビューション ビルドでは、ページの INP 自体に加えて、インタラクションの遅延の原因を把握するのに役立つ多くのデータが提供されます。これには、インタラクションのどの部分に注目すべきかなどが含まれます。次のような重要な情報を得るために役立ちます。
- 「ユーザーはページの読み込み中にページを操作したか?」
- 「インタラクションのイベント ハンドラが長時間実行されたか?」
- 「インタラクション イベント ハンドラ コードの開始が遅れたか。その場合、その時点でメインスレッドで他に何が起きていたか。
- 「インタラクションによって大量のレンダリング処理が発生し、次のフレームの描画が遅れたか?」
次の表に、ライブラリから取得できる基本的なアトリビューション データの一部を示します。これらのデータは、ウェブサイトでのインタラクションが遅くなる原因を特定するのに役立ちます。
attribution オブジェクト キー
|
データ |
|---|---|
interactionTarget
|
ページの INP 値を生成した要素を指す CSS セレクタ(例: button#save)。
|
interactionType
|
インタラクションのタイプ(クリック、タップ、キーボード入力のいずれか)。 |
inputDelay*
|
インタラクションの入力遅延。 |
processingDuration*
|
ユーザー操作に応じて最初のイベント リスナーが実行を開始してから、すべてのイベント リスナーの処理が完了するまでの時間。 |
presentationDelay*
|
インタラクションの表示遅延。イベント ハンドラが終了してから次のフレームが描画されるまでの時間です。 |
longAnimationFrameEntries*
|
インタラクションに関連付けられた LoAF のエントリ。詳しくは、次をご覧ください。 |
web-vitals ライブラリのバージョン 4 以降では、INP フェーズの内訳(入力遅延、処理時間、表示遅延)と 長いアニメーション フレーム API(LoAF)で提供されるデータにより、問題のあるインタラクションについてさらに深い分析情報を得ることができます。
Long Animation Frames API(LoAF)
フィールド データを使用してインタラクションをデバッグするのは、困難な作業です。しかし、LoAF のデータを使用すると、インタラクションが遅い原因をより詳しく把握できるようになりました。LoAF は、詳細なタイミングやその他のデータを多数公開しているため、正確な原因を特定できます。さらに重要なのは、ウェブサイトのコード内の問題の原因を特定できることです。
web-vitals ライブラリのアトリビューション ビルドは、attribution オブジェクトの longAnimationFrameEntries キーの下に LoAF エントリの配列を公開します。次の表に、各 LoAF エントリに含まれる主なデータを示します。
| LoAF エントリ オブジェクト キー | データ |
|---|---|
duration
|
長いアニメーション フレームの継続時間。レイアウトが完了するまでの時間。ペイントと合成は含まれません。 |
blockingDuration
|
長いタスクが原因で、ブラウザが迅速に応答できなかったフレーム内の合計時間。このブロッキング時間には、JavaScript を実行する長いタスクや、フレーム内の後続の長いレンダリング タスクが含まれる場合があります。 |
firstUIEventTimestamp
|
フレーム中にイベントがキューに追加されたときのタイムスタンプ。インタラクションの入力遅延の開始を把握するのに役立ちます。 |
startTime
|
フレームの開始タイムスタンプ。 |
renderStart
|
フレームのレンダリング作業が開始された時刻。これには、requestAnimationFrame コールバック(および該当する場合は ResizeObserver コールバック)が含まれますが、スタイル / レイアウト作業が開始される前になる可能性があります。
|
styleAndLayoutStart
|
フレームでスタイル / レイアウトの処理が発生したとき。他の利用可能なタイムスタンプを考慮する際に、スタイル / レイアウト作業の長さを把握するのに役立ちます。 |
scripts
|
ページの INP に寄与するスクリプトのアトリビューション情報を含むアイテムの配列。 |
blockingDuration を除く)による長いアニメーション フレームのタイミングの図。
これらの情報はすべて、インタラクションが遅くなる原因を特定するのに役立ちますが、LoAF エントリが示す scripts 配列は特に重要です。
| スクリプト アトリビューション オブジェクト キー | データ |
|---|---|
invoker
|
呼び出し元。これは、次の行で説明する呼び出し元のタイプによって異なります。呼び出し元の例としては、'IMG#id.onload'、'Window.requestAnimationFrame'、'Response.json.then' などの値があります。 |
invokerType
|
呼び出し元のタイプ。'user-callback'、'event-listener'、'resolve-promise'、'reject-promise'、'classic-script'、'module-script' のいずれかです。 |
sourceURL
|
長いアニメーション フレームの発生元であるスクリプトの URL。 |
sourceCharPosition
|
sourceURL で識別されるスクリプト内の文字位置。 |
sourceFunctionName
|
特定されたスクリプト内の関数の名前。 |
この配列の各エントリには、この表に示すデータが含まれています。このデータは、インタラクションの遅延の原因となったスクリプトとその原因に関する情報を提供します。
インタラクションが遅くなる一般的な原因を測定して特定する
この情報の活用方法を理解するために、このガイドでは、web-vitals ライブラリに表示される LoAF データを使用して、インタラクションが遅い原因を特定する方法について説明します。
処理時間が長い
インタラクションの処理時間は、インタラクションの登録済みイベント ハンドラ コールバックが完了するまでにかかる時間と、その間に発生する可能性のあるその他の処理にかかる時間です。処理時間が長い場合は、web-vitals ライブラリによって検出されます。
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {processingDuration} = attribution; // 512.5
});
インタラクションが遅くなる主な原因は、イベント ハンドラ コードの実行に時間がかかりすぎていることだと考えるのが自然ですが、必ずしもそうとは限りません。これが問題であることを確認したら、LoAF データを使用してさらに詳しく調べることができます。
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {processingDuration} = attribution; // 512.5
// Get the longest script from LoAF covering `processingDuration`:
const loaf = attribution.longAnimationFrameEntries.at(-1);
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
if (script) {
// Get attribution for the long-running event handler:
const {invokerType} = script; // 'event-listener'
const {invoker} = script; // 'BUTTON#update.onclick'
const {sourceURL} = script; // 'https://example.com/app.js'
const {sourceCharPosition} = script; // 83
const {sourceFunctionName} = script; // 'update'
}
});
上記のコード スニペットからわかるように、LoAF データを使用すると、処理時間の値が高いインタラクションの正確な原因を追跡できます。たとえば、次のような原因を特定できます。
- 要素とその登録済みイベント リスナー。
- 長時間実行イベント ハンドラ コードを含むスクリプト ファイルと、そのファイル内の文字位置。
- 関数名。
この種のデータは非常に貴重です。処理時間の値が高くなった原因となったインタラクション(またはそのイベント ハンドラ)を正確に特定するために、手作業を行う必要がなくなりました。また、サードパーティのスクリプトは独自のイベント ハンドラを登録することが多いため、自分のコードが原因かどうかを判断できます。管理可能なコードについては、長いタスクの最適化を検討してください。
長い入力遅延
長時間実行されるイベント ハンドラは一般的ですが、インタラクションには他にも考慮すべき部分があります。1 つは処理時間の前に発生するもので、入力遅延と呼ばれます。これは、ユーザーがインタラクションを開始してから、イベント ハンドラのコールバックが実行されるまでの時間です。メインスレッドがすでに別のタスクを処理している場合に発生します。web-vitals ライブラリのアトリビューション ビルドを使用すると、インタラクションの入力遅延の長さを確認できます。
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {inputDelay} = attribution; // 125.59439536
});
一部のインタラクションで入力遅延が大きい場合は、インタラクションの発生時にページで何が起こっていたかを把握する必要があります。多くの場合、インタラクションがページの読み込み中に発生したか、読み込み後に発生したかが原因です。
ページの読み込み中でしたか?
メインスレッドは、ページの読み込み中に最もビジーになることがよくあります。この間、あらゆる種類のタスクがキューに入れられて処理されます。この処理がすべて行われている間にユーザーがページを操作しようとすると、操作が遅れる可能性があります。大量の JavaScript を読み込むページでは、スクリプトのコンパイルと評価の処理が開始されるだけでなく、ユーザー インタラクションに備えてページを準備する関数が実行されることもあります。このアクティビティが発生しているときにユーザーが操作を行うと、この処理が妨げになることがあります。ウェブサイトのユーザーにこのような状況が発生しているかどうかは、次の方法で確認できます。
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {inputDelay} = attribution; // 125.59439536
// Get the longest script from the first LoAF entry:
const loaf = attribution.longAnimationFrameEntries[0];
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
if (script) {
// Invoker types can describe if script eval blocked the main thread:
const {invokerType} = script; // 'classic-script' | 'module-script'
const {sourceLocation} = script; // 'https://example.com/app.js'
}
});
このデータをフィールドで記録し、高い入力遅延と 'classic-script' または 'module-script' の呼び出し元タイプが表示される場合は、サイトのスクリプトの評価に時間がかかっており、インタラクションを遅らせるのに十分な時間メインスレッドをブロックしていると言えます。このブロッキング時間を短縮するには、スクリプトを小さなバンドルに分割し、最初は使用しないコードを後で読み込むように延期します。また、サイトを監査して、未使用のコードを完全に削除します。
ページの読み込み後だったか?
入力遅延はページの読み込み中に発生することが多いですが、まったく異なる原因により、ページの読み込み後に発生することもあります。ページ読み込み後の入力遅延の一般的な原因としては、以前の setInterval 呼び出しによって定期的に実行されるコードや、以前に実行されるようにキューに入れられ、まだ処理中のイベント コールバックが考えられます。
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {inputDelay} = attribution; // 125.59439536
// Get the longest script from the first LoAF entry:
const loaf = attribution.longAnimationFrameEntries[0];
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
if (script) {
const {invokerType} = script; // 'user-callback'
const {sourceURL} = script; // 'https://example.com/app.js'
const {sourceCharPosition} = script; // 83
const {sourceFunctionName} = script; // 'update'
}
});
処理時間の値が高い場合のトラブルシューティングと同様に、前述の原因による入力遅延が高い場合は、詳細なスクリプト アトリビューション データが得られます。ただし、異なる点として、インタラクションを遅延させた処理の性質に基づいて、呼び出し元のタイプが変化します。
'user-callback'は、ブロックタスクがsetInterval、setTimeout、requestAnimationFrameのいずれかであることを示します。'event-listener'は、ブロックしているタスクが、キューに格納されてまだ処理中の以前の入力からのものであることを示します。'resolve-promise'と'reject-promise'は、ブロックタスクが、以前に開始された非同期作業によるものであり、ユーザーがページを操作しようとしたときに解決または拒否され、操作が遅延したことを意味します。
いずれにしても、スクリプト アトリビューション データは、どこから調査を始めるべきか、入力遅延の原因が独自のコードなのか、サードパーティ スクリプトなのかを判断するのに役立ちます。
表示の遅延が長い
表示遅延は、インタラクションの最後の段階です。インタラクションのイベント ハンドラが終了してから、次のフレームが描画されるまでの時間を指します。インタラクションによるイベント ハンドラでの処理がユーザー インターフェースの視覚的な状態を変更すると、INP が発生します。処理時間や入力遅延と同様に、web-vitals ライブラリを使用すると、インタラクションの表示遅延を確認できます。
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {presentationDelay} = attribution; // 113.32307691
});
このデータを記録し、ウェブサイトの INP に影響するインタラクションの表示遅延が大きい場合、原因はさまざまですが、注意すべき原因がいくつかあります。
スタイルとレイアウトの処理に時間がかかる
プレゼンテーションの遅延が長い場合は、複雑な CSS セレクタや大きな DOM サイズなど、さまざまな原因で発生するスタイルの再計算とレイアウトの作業にコストがかかる可能性があります。この作業の所要時間は、web-vitals ライブラリに表示される LoAF のタイミングで測定できます。
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {presentationDelay} = attribution; // 113.32307691
// Get the longest script from the last LoAF entry:
const loaf = attribution.longAnimationFrameEntries.at(-1);
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
// Get necessary timings:
const {startTime} = loaf; // 2120.5
const {duration} = loaf; // 1002
// Figure out the ending timestamp of the frame (approximate):
const endTime = startTime + duration; // 3122.5
// Get the start timestamp of the frame's style/layout work:
const {styleAndLayoutStart} = loaf; // 3011.17692309
// Calculate the total style/layout duration:
const styleLayoutDuration = endTime - styleAndLayoutStart; // 111.32307691
if (script) {
// Get attribution for the event handler that triggered
// the long-running style and layout operation:
const {invokerType} = script; // 'event-listener'
const {invoker} = script; // 'BUTTON#update.onclick'
const {sourceURL} = script; // 'https://example.com/app.js'
const {sourceCharPosition} = script; // 83
const {sourceFunctionName} = script; // 'update'
}
});
LoAF では、スタイルとレイアウトの作業がフレームに対して行われた時間はわかりませんが、開始時間はわかります。この開始タイムスタンプを使用すると、LoAF の他のデータを使用して、フレームの終了時間を特定し、そこからスタイルとレイアウトの作業の開始タイムスタンプを差し引くことで、その作業の正確な所要時間を計算できます。
長時間実行の requestAnimationFrame コールバック
プレゼンテーションの遅延が長くなる原因の一つとして、requestAnimationFrame コールバックで過剰な処理が行われていることが考えられます。このコールバックの内容は、イベント ハンドラの実行が完了した後、スタイルの再計算とレイアウト作業の直前に実行されます。
これらのコールバック内で実行される処理が複雑な場合、完了までにかなりの時間がかかることがあります。プレゼンテーション遅延の値が高い原因が requestAnimationFrame で行っている作業にあると思われる場合は、web-vitals ライブラリによって表示される LoAF データを使用して、次のシナリオを特定できます。
onINP(({name, value, attribution}) => {
const {presentationDelay} = attribution; // 543.1999999880791
// Get the longest script from the last LoAF entry:
const loaf = attribution.longAnimationFrameEntries.at(-1);
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
// Get the render start time and when style and layout began:
const {renderStart} = loaf; // 2489
const {styleAndLayoutStart} = loaf; // 2989.5999999940395
// Calculate the `requestAnimationFrame` callback's duration:
const rafDuration = styleAndLayoutStart - renderStart; // 500.59999999403954
if (script) {
// Get attribution for the event handler that triggered
// the long-running requestAnimationFrame callback:
const {invokerType} = script; // 'user-callback'
const {invoker} = script; // 'FrameRequestCallback'
const {sourceURL} = script; // 'https://example.com/app.js'
const {sourceCharPosition} = script; // 83
const {sourceFunctionName} = script; // 'update'
}
});
プレゼンテーション遅延時間の大部分が requestAnimationFrame コールバックで費やされている場合は、これらのコールバックで行っている作業が、ユーザー インターフェースの実際の更新につながる作業の実行に限定されていることを確認してください。DOM に触れたりスタイルを更新したりしないその他の作業は、次のフレームの描画を不必要に遅らせることになるため、注意が必要です。
まとめ
フィールド データは、実際のユーザーにとって問題となるインタラクションを把握するうえで、最も信頼できる情報源です。web-vitals JavaScript ライブラリ(または RUM プロバイダ)などのフィールド データの収集ツールを使用すると、どのインタラクションに最も問題があるかをより正確に把握できます。その後、ラボで問題のあるインタラクションを再現し、修正に取り掛かることができます。