Core Web Vitalsの計測アルゴリズム(LCP/CLS/INP)
なぜその数値になるのかを内部から説明できるようになる。LCP候補の確定、CLSの impact×distance、INPの遅延定義というブラウザ内部のアルゴリズムを正確に解説します。
- 1.LCPはビューポート内に描画された要素のうち面積最大のものを毎フレーム候補として記録し、最初のユーザー操作で確定する。テキストノードや背景画像など除外規則も厳密に定義されている。
- 2.CLSのレイアウトシフトスコアは impact fraction(影響領域の割合)× distance fraction(最大移動量の割合)。これをセッションウィンドウ(最大5秒・隙間1秒)でまとめ、その最大値が最終CLSになる。
- 3.INPは入力遅延(input delay)+処理時間(processing time)+表示遅延(presentation delay)の合計で、ページ滞在中の全インタラクションのうちほぼ最悪値(98パーセンタイル相当)を採用する。
計測の土台:PerformanceObserver と描画フレーム
Core Web Vitals の3指標(LCP / CLS / INP)は、いずれもブラウザが内部で持つ計測パイプラインから PerformanceObserver 経由で値を取り出します。共通する原理は、描画フレームごとにブラウザが状態を観測し、エントリとして記録することです。JavaScript で集計しているのではなく、レンダリングエンジンが各段で計測したものを後から読むだけです。基礎の描画工程は レンダリングパイプライン詳説 を参照してください。
// 3指標はそれぞれ専用のエントリ種別で観測する
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// entry.entryType: "largest-contentful-paint" / "layout-shift" / "event"
}
}).observe({ type: "largest-contentful-paint", buffered: true });
重要なのは、これらがフィールド計測(実利用者)でも同じ定義で動く点です。Lighthouse のラボ値と PageSpeed Insights のフィールド値がずれるのは、計測アルゴリズムが違うからではなく、入力(端末・回線・操作)が違うからです。
LCP:最大コンテンツ要素の候補確定ロジック
LCP(Largest Contentful Paint)は「ビューポート内に表示されたコンテンツ要素のうち面積が最大のものが描画された時刻」です。ポイントは、これが一発で決まらず逐次更新される候補である点です。
ブラウザは描画のたびに、対象となる要素群の中から面積最大のものを LCP 候補として記録します。対象になるのは次の要素です。
| 対象になる要素 | 備考 |
|---|---|
| img 要素 | 面積は表示サイズと固有サイズの小さい方で算出 |
| image を伴う svg 内の画像 | 外部リソースとして読み込まれる画像 |
| video のポスター画像 | video 自体ではなくポスター |
| background-image を持つブロック | url() による背景画像のみ |
| テキストを含むブロックレベル要素 | テキストノードを内包する最小ブロック |
候補のサイズはビューポート内に見えている部分の面積で測ります。はみ出した分や overflow で隠れた分は数えません。さらに精度を保つため、いくつかの除外規則があります。
- 不透明度0の要素は対象外。
opacity: 0で描かれた要素はサイズ0として扱われる。 - ビューポート全体を覆う背景(プレースホルダ的な巨大背景)は除外される。
- 画像の実効サイズは表示面積に制限される。巨大画像を小さく縮小表示しても、表示サイズ分しか加算されない(高密度の引き伸ばしを稼げないようにする補正)。
候補は最初のユーザー操作(クリック・キー入力・スクロール)で確定します。操作以降に大きな要素が現れても LCP は更新されません。これは「ユーザーが触れた=主要コンテンツは見えたとみなす」という設計です。
画像のローディングが遅れて低解像度プレースホルダ→本画像と差し替わる場合、より大きい候補が出るたびに LCP 時刻は更新されます。一方、既存候補がDOMから削除されても過去の候補時刻は取り消されません(消えた要素も「一度は見えた」ため)。この非対称性が、体感とLCP値のズレを生む典型原因です。
CLS:レイアウトシフトスコアの計算式
CLS(Cumulative Layout Shift)は「視覚的な安定性」の指標で、個々のレイアウトシフトのスコアを合算したものです。1回のシフトのスコアは次式で定義されます。
layout shift score = impact fraction × distance fraction
- impact fraction(影響領域の割合):シフト前の位置とシフト後の位置を和集合した領域が、ビューポート面積に占める割合。たとえば高さ50%の要素が下に25%動くと、和集合は75%=impact fraction は
0.75。 - distance fraction(移動距離の割合):そのフレームで動いた不安定要素の最大移動量を、ビューポートの幅または高さ(移動方向に対応する側)で割った割合。上の例で25%下に動いたなら distance fraction は
0.25。
この2つの積がそのシフトのスコアです(例では 0.75 × 0.25 = 0.1875)。distance fraction を掛けるのは、広い領域でも少ししか動かないシフトは体感影響が小さいことを反映するためです(2019年の改定で impact だけの式から変更されました)。
数えるのは予期しないシフトだけです。ユーザー操作の直後500ミリ秒以内に起きたシフトは「操作に起因する想定内の変化」として除外されます(hadRecentInput フラグ)。
セッションウィンドウによる合算
初期の CLS は全シフトの単純合計だったため、長く滞在するページや無限スクロールで不利でした。現在はセッションウィンドウ方式です。
| パラメータ | 値 | 意味 |
|---|---|---|
| ウィンドウ最大長 | 5秒 | 1つのウィンドウが伸びられる上限 |
| シフト間ギャップ | 1秒 | この間隔が空くと新しいウィンドウを開始 |
| 最終CLS | 最大ウィンドウの合計 | 全ウィンドウ中で合計が最大のもの |
連続するシフトを1つのウィンドウにまとめ、ギャップが1秒空くか、ウィンドウ開始から5秒経過すると次のウィンドウへ切り替えます。各ウィンドウ内のスコア合計のうち最も大きい値がそのページの CLS になります。これにより、滞在時間が長くても1回のひどい瞬間で評価される設計になっています。
INP:インタラクション遅延の内部定義
INP(Interaction to Next Paint)は、FID に代わる応答性の指標です。FID が最初の入力の入力遅延だけを測ったのに対し、INP は操作してから次に画面が更新される(次のペイント)までの全体を測ります。1回のインタラクションの遅延は3つの区間の合計です。
interaction latency = input delay + processing time + presentation delay
- input delay(入力遅延):ユーザーが操作した瞬間から、対応するイベントハンドラが走り始めるまでの待ち時間。メインスレッドが他の長いタスクで占有されていると延びる。背景は イベントループ詳説 を参照。
- processing time(処理時間):イベントハンドラ(複数あればその連鎖)が実行されている時間。
pointerdown/pointerup/clickなど、1つの論理操作に紐づく全ハンドラを含む。 - presentation delay(表示遅延):ハンドラ完了後、ブラウザがスタイル計算・レイアウト・ペイントを終え、次のフレームを描くまでの時間。ハンドラがDOMを大きく書き換えるとここが延びる。
1つの「インタラクション」は入力イベントのグループとして束ねられます。たとえばタップは pointerdown → pointerup → click をまとめて1インタラクション、キー入力は keydown → keypress → keyup を束ね、それらに属するハンドラの中で最も遅延が大きいイベントの値をそのインタラクションの遅延とします。mousemove のような連続イベントは対象外です。
FID は「最初の操作が反応を始めるまでの遅延」だけを見ており、ハンドラの実行時間や再描画コストを無視していました。そのため重いハンドラを持つページでも FID は良好に出がちでした。INP は処理時間と表示遅延まで含め、ページ全滞在中の全操作を評価対象にすることで、実際の「押してから変わるまで」を捉えます。
ページ全体の INP 値の決め方
ページ滞在中に多数のインタラクションが起きます。INP として報告されるのは、その中のほぼ最悪値です。具体的には、インタラクション数に応じて外れ値を間引いた98パーセンタイル相当を採用します。
インタラクション総数 N に対し
floor(N / 50) 個の最悪エントリを除外し
残りの中で最も遅いものを INP とする
50回ごとに最悪1件を捨てるため、操作回数が少ないうちは実質的に最大値、多くなると上位の外れ値を1件ずつ無視します。単発のたまたまのスパイクで全体評価が決まらないようにする補正です。
良好の閾値とパーセンタイル評価
| 指標 | Good | Poor | 集計の母数 |
|---|---|---|---|
| LCP | 2.5秒以下 | 4.0秒超 | 最大コンテンツ要素の描画時刻 |
| CLS | 0.1以下 | 0.25超 | 最大セッションウィンドウの合計 |
| INP | 200ミリ秒以下 | 500ミリ秒超 | 全インタラクションのほぼ最悪値 |
フィールド評価では、これらの値を全訪問の75パーセンタイルで見ます。つまり「訪問の75%が Good 閾値以下」で初めて合格扱いです。平均ではなくパーセンタイルを使うのは、一部の遅い体験を平均が覆い隠すのを避けるためです。
試験・面接で問われやすいのは、(1) LCP候補が毎フレーム更新され最初の操作で確定する点と除外規則、(2) CLSスコアが impact × distance であることと500ミリ秒の操作除外・セッションウィンドウ(5秒/1秒)、(3) INPの3区間(input delay+processing+presentation)と FID との差、(4) フィールド評価が75パーセンタイル・INPが98パーセンタイル相当、の4点です。
まとめ
LCPはビューポート内の面積最大のコンテンツ要素を毎フレーム候補として記録し、不透明度0や全画面背景を除外しつつ最初のユーザー操作で確定します。CLSは各シフトを impact fraction × distance fraction で採点し、操作直後500ミリ秒を除外してセッションウィンドウ(最大5秒・ギャップ1秒)でまとめた最大合計を取ります。INPは input delay+processing time+presentation delay の合計で、全インタラクションの98パーセンタイル相当を採用します。いずれもフィールドでは75パーセンタイルで良否を判定します。実務の改善手順は Web パフォーマンス を、応答性の根にあるメインスレッド占有は イベントループ詳説 を合わせて押さえてください。
Web/フロントエンド Article
Core Web Vitalsの計測アルゴリズム(LCP/CLS/INP)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Core Web Vitals
比較で見る軸
難易度: advanced / カテゴリ: Web/フロントエンド / タグ数: 5
導入後に効く点
CLSのレイアウトシフトスコアは impact fraction(影響領域の割合)× distance fraction(最大移動量の割合)。これをセッションウィンドウ(最大5秒・隙間1秒)でまとめ、その最大値が最終CLSになる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- Web/フロントエンド
- タグ数
- 5
判断チェックリスト
- 自社の用途が「Core Web Vitals / パフォーマンス」に近いか確認する。
- 強みである「LCPはビューポート内に描画された要素のうち面積最大のものを毎フレーム候補として記録し、最初のユーザー操作で確定する。テキストノードや背景画像など除外規則も厳密に定義されている。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。