1. はじめに
このインタラクティブな Codelab では、web-vitals ライブラリを使用して Interaction to Next Paint(INP)を測定する方法を学びます。
前提条件
- HTML と JavaScript の開発に関する知識。
- 推奨: web.dev の INP 指標に関するドキュメントをお読みください。
学習内容
- ページに
web-vitalsライブラリを追加して、アトリビューション データを使用する方法。 - アトリビューション データを使用して、INP の改善をどこからどのように始めるかを診断します。
必要なもの
- GitHub からコードのクローンを作成し、npm コマンドを実行できるパソコン。
- テキスト エディタ。
- すべてのインタラクション測定が機能する Chrome の最新バージョン。
2. セットアップする
コードを取得して実行する
このコードは web-vitals-codelabs リポジトリにあります。
- ターミナルでリポジトリのクローンを作成します(
git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git)。 - クローン作成したディレクトリ
cd web-vitals-codelabs/measuring-inpに移動します。 - 依存関係をインストールします:
npm ci。 - ウェブサーバーを起動します:
npm run start。 - ブラウザで http://localhost:8080/ にアクセスします。
ページを試す
この Codelab では、Gastropodicon(人気のカタツムリの解剖学に関する参考サイト)を使用して、INP の潜在的な問題を検証します。

ページを操作して、どの操作が遅いかを確認します。
3. Chrome DevTools の概要
DevTools を開きます。[その他のツール] > [デベロッパー ツール] メニューから開くか、ページを右クリックして [検証] を選択するか、キーボード ショートカットを使用します。
この Codelab では、[パフォーマンス] パネルと [コンソール] の両方を使用します。これらは、DevTools の上部にあるタブでいつでも切り替えることができます。
- INP の問題はモバイル デバイスで発生することが多いため、モバイル ディスプレイ エミュレーションに切り替えます。
- デスクトップ パソコンやノートパソコンでテストする場合、パフォーマンスは実際のモバイル デバイスよりも大幅に向上する可能性があります。パフォーマンスをより現実的に確認するには、[パフォーマンス] パネルの右上にある歯車アイコンをクリックし、[CPU 4 倍の減速] を選択します。
![DevTools の [パフォーマンス] パネルとアプリのスクリーンショット。4 倍の CPU スローダウンが選択されている](https://web.dev/static/codelabs/measuring-inp/img/01_screenshot.png?hl=ja)
4. web-vitals のインストール
web-vitals は、ユーザーが体験するウェブに関する主な指標を測定するための JavaScript ライブラリです。このライブラリを使用すると、これらの値をキャプチャし、分析エンドポイントにビーコンを送信して後で分析できます。この分析の目的は、遅延が発生するタイミングと場所を特定することです。
ライブラリをページに追加する方法はいくつかあります。ライブラリをサイトにインストールする方法は、依存関係の管理方法やビルドプロセスなどの要因によって異なります。すべてのオプションについては、ライブラリのドキュメントをご覧ください。
この Codelab では、特定のビルドプロセスに踏み込まないように、npm からインストールしてスクリプトを直接読み込みます。
使用できる web-vitals には次の 2 つのバージョンがあります。
- ページの読み込み時にウェブに関する主な指標の指標値をトラッキングする場合は、「標準」ビルドを使用する必要があります。
- 「アトリビューション」ビルドでは、各指標にデバッグ情報が追加され、指標の値がその値になった理由を診断できます。
この Codelab で INP を測定するには、アトリビューション ビルドが必要です。
npm install -D web-vitals を実行して、プロジェクトの devDependencies に web-vitals を追加します。
ページに web-vitals を追加します。
スクリプトのアトリビューション バージョンを index.html の下部に追加し、結果をコンソールに記録します。
<script type="module">
import {onINP} from './node_modules/web-vitals/dist/web-vitals.attribution.js';
onINP(console.log);
</script>
試してみる
コンソールを開いた状態で、ページをもう一度操作してみます。ページ内をクリックしても、何も記録されません。
INP はページのライフサイクル全体で測定されるため、デフォルトでは、ユーザーがページを離れるか閉じるまで web-vitals は INP を報告しません。これは、分析などのビーコン送信には理想的な動作ですが、インタラクティブなデバッグにはあまり適していません。
web-vitals は、詳細なレポート用の reportAllChanges オプションを提供します。有効にすると、すべてのインタラクションがレポートされるわけではありませんが、先行するインタラクションよりも遅いインタラクションが発生するたびにレポートされます。
スクリプトにオプションを追加して、ページをもう一度操作してみてください。
<script type="module">
import {onINP} from './node_modules/web-vitals/dist/web-vitals.attribution.js';
onINP(console.log, {reportAllChanges: true});
</script>
ページを更新すると、インタラクションがコンソールに報告され、最も遅いインタラクションが更新されるたびに更新されるようになります。たとえば、検索ボックスに入力してから、入力を削除してみてください。

5. アトリビューションの内容
ほとんどのユーザーがページにアクセスしたときに最初に表示される Cookie 使用の同意ダイアログから始めましょう。
多くのページには、ユーザーが Cookie を受け入れたときに同期的にトリガーされる Cookie を必要とするスクリプトが含まれているため、クリックが遅いインタラクションになります。これが、ここで起こることです。
[はい] をクリックして(デモ)Cookie を受け入れ、DevTools コンソールに記録された INP データを表示します。

この最上位の情報は、標準とアトリビューションの両方のウェブバイタル ビルドで利用できます。
{
name: 'INP',
value: 344,
rating: 'needs-improvement',
entries: [...],
id: 'v4-1715732159298-8028729544485',
navigationType: 'reload',
attribution: {...},
}
ユーザーがクリックしてから次のペイントまでの時間は 344 ミリ秒で、改善が必要な INP です。entries 配列には、このインタラクションに関連付けられているすべての PerformanceEntry 値が含まれています。この例では、クリック イベントが 1 つだけです。
この期間中に何が起こっているかを確認するには、attribution プロパティが最も重要です。アトリビューション データを構築するために、web-vitals はクリック イベントと重複する Long Animations Frame(LoAF) を特定します。LoAF は、実行されたスクリプトから requestAnimationFrame コールバック、スタイル、レイアウトに費やされた時間まで、そのフレームで時間がどのように費やされたかについての詳細なデータを提供します。
attribution プロパティを開いて詳細情報を表示します。データがはるかに豊富になります。
attribution: {
interactionTargetElement: Element,
interactionTarget: '#confirm',
interactionType: 'pointer',
inputDelay: 27,
processingDuration: 295.6,
presentationDelay: 21.4,
processedEventEntries: [...],
longAnimationFrameEntries: [...],
}
まず、インタラクションの対象に関する情報があります。
interactionTargetElement: 操作された要素へのライブ参照(要素が DOM から削除されていない場合)。interactionTarget: ページ内の要素を見つけるためのセレクタ。
次に、タイミングを大まかに分類します。
inputDelay: ユーザーが操作を開始したとき(マウスをクリックしたときなど)から、その操作のイベント リスナーが実行を開始するまでの時間。この場合、CPU スロットリングがオンになっていても、入力遅延は約 27 ミリ秒でした。processingDuration: イベント リスナーが実行を完了するまでの時間。多くの場合、ページには単一のイベント(pointerdown、pointerup、clickなど)に対する複数のリスナーがあります。これらがすべて同じアニメーション フレームで実行される場合、この時間に統合されます。この場合、処理時間は 295.6 ミリ秒で、INP 時間の大部分を占めています。presentationDelay: イベント リスナーが完了してから、ブラウザが次のフレームのペイントを完了するまでの時間。この例では 21.4 ミリ秒です。
これらの INP フェーズは、最適化が必要なものを診断するための重要なシグナルになります。INP の最適化に関するガイドで、このトピックについて詳しく説明しています。
さらに詳しく見てみると、最上位の INP entries 配列の単一のイベントとは異なり、processedEventEntries には 5 つのイベントが含まれています。の機能上の違い
processedEventEntries: [
{
name: 'mouseover',
entryType: 'event',
startTime: 1801.6,
duration: 344,
processingStart: 1825.3,
processingEnd: 1825.3,
cancelable: true
},
{
name: 'mousedown',
entryType: 'event',
startTime: 1801.6,
duration: 344,
processingStart: 1825.3,
processingEnd: 1825.3,
cancelable: true
},
{name: 'mousedown', ...},
{name: 'mouseup', ...},
{name: 'click', ...},
],
最上位のエントリは INP イベント(この場合はクリック)です。アトリビューション processedEventEntries は、同じフレームで処理されたすべてのイベントです。クリック イベントだけでなく、mouseover や mousedown などの他のイベントも含まれていることに注意してください。これらの他のイベントも遅かった場合、それらはすべて応答性の低下に寄与しているため、これらのイベントについて知ることは非常に重要です。
最後に、longAnimationFrameEntries 配列があります。これは単一のエントリの場合もありますが、インタラクションが複数のフレームにまたがる場合もあります。ここでは、単一の長いアニメーション フレームを含む最もシンプルなケースを扱います。
longAnimationFrameEntries
LoAF エントリを開く:
longAnimationFrameEntries: [{
name: 'long-animation-frame',
startTime: 1823,
duration: 319,
renderStart: 2139.5,
styleAndLayoutStart: 2139.7,
firstUIEventTimestamp: 1801.6,
blockingDuration: 268,
scripts: [{...}]
}],
ここでは、スタイリングに費やした時間など、さまざまな有用な値を確認できます。Long Animation Frames API の記事では、これらのプロパティについて詳しく説明しています。現時点では、主に scripts プロパティに関心があります。このプロパティには、長時間実行されているフレームの原因となったスクリプトの詳細を提供するエントリが含まれています。
scripts: [{
name: 'script',
invoker: 'BUTTON#confirm.onclick',
invokerType: 'event-listener',
startTime: 1828.6,
executionStart: 1828.6,
duration: 294,
sourceURL: 'http://localhost:8080/third-party/cmp.js',
sourceFunctionName: '',
sourceCharPosition: 1144
}]
この場合、主に 1 つの event-listener で時間が費やされ、BUTTON#confirm.onclick で呼び出されたことがわかります。スクリプトのソース URL と、関数が定義された文字位置も確認できます。
重要なポイント
このアトリビューション データから、このケースについてどのようなことがわかりますか?
- インタラクションは、
button#confirm要素(attribution.interactionTargetとスクリプト アトリビューション エントリのinvokerプロパティ)のクリックによってトリガーされました。 - 主にイベント リスナーの実行に時間が費やされました(合計指標
valueと比較したattribution.processingDuration)。 - 遅いイベント リスナーのコードは、
third-party/cmp.js(scripts.sourceURLから)で定義されたクリック リスナーから始まります。
これで、最適化が必要な場所を特定するのに十分なデータが得られました。
6. 複数のイベント リスナー
DevTools コンソールがクリアになり、Cookie 使用の同意のインタラクションが最長のインタラクションではなくなるように、ページを更新します。
検索ボックスに入力を開始します。アトリビューション データには何が表示されますか?何が起きていると思いますか?
アトリビューション データ
まず、デモのテストの例の概要を見てみましょう。
{
name: 'INP',
value: 1072,
rating: 'poor',
attribution: {
interactionTargetElement: Element,
interactionTarget: '#search-terms',
interactionType: 'keyboard',
inputDelay: 3.3,
processingDuration: 1060.6,
presentationDelay: 8.1,
processedEventEntries: [...],
longAnimationFrameEntries: [...],
}
}
これは、input#search-terms 要素とのキーボード操作による INP の値が低い(CPU スロットリングが有効になっている)例です。時間の大部分(合計 INP 1,072 ミリ秒のうち 1,061 ミリ秒)は処理時間に費やされました。
ただし、scripts エントリはより興味深いものです。
レイアウト スラッシング
scripts 配列の最初のエントリには、次のような重要なコンテキストが含まれています。
scripts: [{
name: 'script',
invoker: 'BUTTON#confirm.onclick',
invokerType: 'event-listener',
startTime: 4875.6,
executionStart: 4875.6,
duration: 497,
forcedStyleAndLayoutDuration: 388,
sourceURL: 'http://localhost:8080/js/index.js',
sourceFunctionName: 'handleSearch',
sourceCharPosition: 940
},
...]
処理時間の大部分は、このスクリプトの実行中(input リスナー、呼び出し元は INPUT#search-terms.oninput)に発生します。関数名(handleSearch)と index.js ソースファイル内の文字位置が指定されています。
ただし、forcedStyleAndLayoutDuration という新しいプロパティがあります。これは、ブラウザがページの再レイアウトを強制された、このスクリプト呼び出し内の時間です。つまり、このイベント リスナーの実行に費やされた時間の 78%(497 ミリ秒のうち 388 ミリ秒)は、レイアウト スラッシングに費やされたことになります。
これは最優先で修正する必要があります。
リピーター
次の 2 つのスクリプト エントリは、個別に見て特に注目すべき点はありません。
scripts: [...,
{
name: 'script',
invoker: '#document.onkeyup',
invokerType: 'event-listener',
startTime: 5375.3,
executionStart: 5375.3,
duration: 124,
sourceURL: 'http://localhost:8080/js/index.js',
sourceFunctionName: '',
sourceCharPosition: 1526,
},
{
name: 'script',
invoker: '#document.onkeyup',
invokerType: 'event-listener',
startTime: 5673.9,
executionStart: 5673.9,
duration: 95,
sourceURL: 'http://localhost:8080/js/index.js',
sourceFunctionName: '',
sourceCharPosition: 1526
}]
どちらのエントリも keyup リスナーであり、連続して実行されます。リスナーは匿名関数であるため(sourceFunctionName プロパティには何も報告されません)、ソースファイルと文字位置は引き続き存在するため、コードの場所を特定できます。
奇妙なのは、両方とも同じソースファイルと文字位置から来ていることです。
ブラウザは、1 つのアニメーション フレーム内で複数のキープレスを処理したため、このイベント リスナーは、何かが描画される前に 2 回実行されました。
この影響は複合的に発生することもあります。イベント リスナーの完了に時間がかかるほど、追加の入力イベントがさらに多く発生し、遅いインタラクションがさらに長くなります。
これは検索/オートコンプリートのインタラクションであるため、入力のデバウンスは、フレームごとに最大 1 回のキープレスが処理されるようにするための優れた戦略です。
7. 入力遅延
入力遅延(ユーザーが操作してからイベント リスナーが操作の処理を開始できるまでの時間)の一般的な原因は、メインスレッドがビジー状態であることです。これには複数の原因が考えられます。
- ページが読み込まれ、メインスレッドが DOM の設定、ページのレイアウトとスタイルの設定、スクリプトの評価と実行の初期作業でビジー状態になっています。
- ページが一般的にビジー状態である(計算、スクリプトベースのアニメーション、広告の実行など)。
- 前回のやり取りの処理に時間がかかり、今後のやり取りが遅延する(最後の例を参照)。
デモページには、ページ上部のカタツムリのロゴをクリックするとアニメーションが始まり、メインスレッドで JavaScript の重い処理が行われるという秘密の機能があります。
- カタツムリのロゴをクリックしてアニメーションを開始します。
- JavaScript タスクは、カタツムリがバウンドの最下部に達したときにトリガーされます。バウンスの直前でページを操作してみて、どの程度の INP が発生するかを確認します。
たとえば、カタツムリが跳ね返った直後に検索ボックスをクリックしてフォーカスを移動するなど、他のイベント リスナーをトリガーしなかったとしても、メインスレッドの処理によってページが応答しなくなる時間が長くなります。
多くのページでは、メインスレッドの重い処理はこれほど適切に動作しませんが、INP 属性データでどのように識別できるかを確認するのに適したデモです。
以下は、カタツムリ バウンス中に検索ボックスのみにフォーカスした場合の帰属の例です。
{
name: 'INP',
value: 728,
rating: 'poor',
attribution: {
interactionTargetElement: Element,
interactionTarget: '#search-terms',
interactionType: 'pointer',
inputDelay: 702.3,
processingDuration: 4.9,
presentationDelay: 20.8,
longAnimationFrameEntries: [{
name: 'long-animation-frame',
startTime: 2064.8,
duration: 790,
renderStart: 2065,
styleAndLayoutStart: 2854.2,
firstUIEventTimestamp: 0,
blockingDuration: 740,
scripts: [{...}]
}]
}
}
予測どおり、イベント リスナーはすばやく実行され、処理時間は 4.9 ミリ秒でした。インタラクションの大部分は入力遅延に費やされ、合計 728 ミリ秒のうち 702.3 ミリ秒を占めていました。
この状況はデバッグが難しい場合があります。ユーザーが何にどのように操作したかはわかっていますが、その操作はすぐに完了しており、問題は発生していません。実際には、ページ上の別の要素が処理の開始を遅らせていたのですが、どこから調べ始めればよいのでしょうか?
LoAF スクリプト エントリは、このような状況を解決するために用意されています。
scripts: [{
name: 'script',
invoker: 'SPAN.onanimationiteration',
invokerType: 'event-listener',
startTime: 2065,
executionStart: 2065,
duration: 788,
sourceURL: 'http://localhost:8080/js/index.js',
sourceFunctionName: 'cryptodaphneCoinHandler',
sourceCharPosition: 1831
}]
この関数はインタラクションとは関係ありませんが、アニメーション フレームを遅くしたため、インタラクション イベントと結合された LoAF データに含まれています。
ここから、インタラクション処理を遅延させた関数がどのようにトリガーされたか(animationiteration リスナーによる)、どの関数が原因か、ソースファイル内のどこにあるかを確認できます。
8. プレゼンテーションの遅延: 更新が描画されない場合
プレゼンテーション遅延は、イベント リスナーの実行が完了してから、ブラウザが新しいフレームを画面に描画してユーザーにフィードバックを表示できるようになるまでの時間を測定します。
ページを更新して INP 値をもう一度リセットし、ハンバーガー メニューを開きます。開くときに必ず引っかかります。
これはどのような状況でしょうか。
{
name: 'INP',
value: 376,
rating: 'needs-improvement',
delta: 352,
attribution: {
interactionTarget: '#sidenav-button>svg',
interactionType: 'pointer',
inputDelay: 12.8,
processingDuration: 14.7,
presentationDelay: 348.5,
longAnimationFrameEntries: [{
name: 'long-animation-frame',
startTime: 651,
duration: 365,
renderStart: 673.2,
styleAndLayoutStart: 1004.3,
firstUIEventTimestamp: 138.6,
blockingDuration: 315,
scripts: [{...}]
}]
}
}
今回は、遅延の大部分を占めるのはプレゼンテーションの遅延です。つまり、メインスレッドをブロックするものは、イベント リスナーが完了した後に発生します。
scripts: [{
entryType: 'script',
invoker: 'FrameRequestCallback',
invokerType: 'user-callback',
startTime: 673.8,
executionStart: 673.8,
duration: 330,
sourceURL: 'http://localhost:8080/js/side-nav.js',
sourceFunctionName: '',
sourceCharPosition: 1193,
}]
scripts 配列の単一のエントリを見ると、FrameRequestCallback の user-callback で時間が費やされていることがわかります。今回は、requestAnimationFrame コールバックが原因でプレゼンテーションの遅延が発生しています。
9. まとめ
フィールド データの集計
1 回のページ読み込みから 1 つの INP アトリビューション エントリを調べる場合は、すべてが簡単になることを認識しておく必要があります。このデータを集計して、フィールド データに基づいて INP をデバッグするにはどうすればよいですか?詳細な情報が多すぎると、かえって難しくなります。
たとえば、どのページ要素がインタラクションの遅延の一般的な原因となっているかを把握することは非常に有用です。ただし、ページにビルドごとに変更されるコンパイル済みの CSS クラス名がある場合、同じ要素の web-vitals セレクタがビルド間で異なる可能性があります。
代わりに、特定のアプリケーションを考慮して、最も有用なデータとデータの集計方法を判断する必要があります。たとえば、アトリビューション データをビーコンで送信する前に、ターゲットが存在するコンポーネントやターゲットが満たす ARIA ロールに基づいて、web-vitals セレクタを独自の識別子に置き換えることができます。
同様に、scripts エントリの sourceURL パスにファイルベースのハッシュが含まれていると、結合が難しくなる可能性がありますが、既知のビルドプロセスに基づいてハッシュを削除してから、データをビーコンで送信し直すことができます。
残念ながら、これほど複雑なデータで簡単な方法はありませんが、デバッグ プロセスでは、データの一部を使用するだけでも、アトリビューション データがまったくない場合よりも価値があります。
どこでもクレジット表示!
LoAF ベースの INP アトリビューションは、強力なデバッグ支援機能です。INP の間に具体的に何が起こったかについての詳細なデータを提供します。多くの場合、最適化の取り組みを開始するスクリプト内の正確な場所を特定できます。
これで、どのサイトでも INP アトリビューション データを使用できるようになりました。
ページを編集する権限がない場合でも、DevTools コンソールで次のスニペットを実行して、この Codelab のプロセスを再現し、検出結果を確認できます。
const script = document.createElement('script');
script.src = 'https://unpkg.com/web-vitals@4/dist/web-vitals.attribution.iife.js';
script.onload = function () {
webVitals.onINP(console.log, {reportAllChanges: true});
};
document.head.appendChild(script);