1. はじめに
このセクションは非規範的です。
ウェブページ上で DOM 要素が移動することは、ユーザー体験を損なう原因となり、 現在のウェブで頻繁に発生しています。この移動は、コンテンツが非同期に 読み込まれてページ上の他の要素を押しのけてしまうことが主な要因です。
Layout Instability API は、ユーザーのセッション中の各アニメーションフレーム ごとに値(「レイアウトシフト」)を報告することで、こうした不安定なページを 特定します。本仕様では、ユーザーエージェントがレイアウトシフト値を 計算する方法を提示します。
レイアウトシフト値は、ある時点でのレイアウト不安定性の深刻度に大まかに 対応することが期待されています。その計算方法は、不安定さの影響を受けた 領域の面積と、ページ上の要素が移動した距離の両方を考慮します。
本仕様で公開される値は、「レイアウト変更オブザーバー」として 用いることを意図していません。その理由は二つあります。 一つ目は、これらの値は PerformanceObserver に結び付けられており、 サイトのパフォーマンスへの影響を避けるため、必要と判断すれば ユーザーエージェントによってコールバックの実行が遅延される場合があることです。 二つ目は、非常に小さなレイアウトシフトはユーザーエージェントに よって無視される可能性があることです。従って、ウェブサイトの ユーザーに見える挙動へ影響する JavaScript を実行する手段として この API に依存することは推奨されません。
1.1. 累積レイアウトシフト(CLS)
このセクションは非規範的です。
レイアウトシフト値 は単一時点の値を表しますが、ユーザーがページで過ごす期間全体の 不安定性を示す値を持つことも有用です。
そのため、私たちはユーザーエージェントや開発者がそのような表現を得るために計算できる 2 つの値を提案します。(これらの定義は API では公開されないため非規範的です。)
-
document cumulative layout shift(DCLS) スコアは、 単一のブラウジングコンテキスト内で 報告される全ての レイアウトシフト値の合計です。 (DCLS スコアは子孫ブラウジングコンテキスト内の不安定性は考慮しません。)
-
累積レイアウトシフト(CLS)スコアは、 レイアウトシフト値 のうち、 トップレベルのブラウジングコンテキスト内の全ての合計と、 レイアウトシフト値の うち、 子孫ブラウジングコンテキスト内の各値 に対し サブフレーム重み付け係数 の割合を 加算したものです。
-
サブフレーム重み付け係数は、 レイアウトシフト値 が 子ブラウジングコンテキストにあるとき、 その子のビューポートがトップレベル ビューポートの中で占める割合です。
累積レイアウトシフトスコアは、ページのライフタイム全体に対する レイアウト不安定性の深刻度を大まかに示すものとなります。
開発者はこの API を利用し、レポートされた値を合計して DCLS や CLS スコアを計算し、 visibilitychange イベント発火時に 「最終」スコアを取得することができます。
この方法は使用例に示されています。
1.2. 原因帰属
このセクションは非規範的です。
レイアウトシフト値に加えて、この API はアニメーションフレームごとの レイアウトシフト値に最も大きく寄与した最大5つの DOM 要素のサンプリングを 報告します。sources は影響領域が大きい順に並べられており、 最初の要素がレイアウトシフトに最も寄与した要素を表します。
実際の「根本原因」はレイアウトシフトを受けた DOM 要素と 間接的にのみ関係している場合があります。例えば、新しく挿入された 要素によって下のコンテンツがずれた場合、sources 属性には ずれた要素だけが報告され、挿入された要素は含まれません。
ユーザーエージェントが本当の意味での「根本原因」特定に必要な 間接性のレベルまで不安定性の原因を理解することは、現実的でないと考えます。 しかし、この API が提供するシフト要素の単純なレポートでも、 レイアウト不安定発生時の原因究明に取り組む開発者にとって十分価値が あると期待しています。
1.3. 使用例
このセクションは非規範的です。
let perFrameLayoutShiftData= []; let cumulativeLayoutShiftScore= 0 ; function updateCLS( entries) { for ( const entryof entries) { // Only count layout shifts without recent user input. if ( entry. hadRecentInput) return ; perFrameLayoutShiftData. push({ score: entry. value, timestamp: entry. startTime}); cumulativeLayoutShiftScore+= entry. value; // Sources are sorted by impact area in descending order. // The first element contributed most to the layout shift. if ( entry. sources&& entry. sources. length> 0 ) { console. log( 'Largest contributing element:' , entry. sources[ 0 ]. node); } } } // Observe all layout shift occurrences. const observer= new PerformanceObserver(( list) => { updateCLS( list. getEntries()); }); observer. observe({ type: 'layout-shift' , buffered: true }); // Send final data to an analytics back end once the page is hidden. document. addEventListener( 'visibilitychange' , () => { if ( document. visibilityState=== 'hidden' ) { // Force any pending records to be dispatched. updateCLS( observer. takeRecords()); // Send data to your analytics back end (assumes `sendToAnalytics` is // defined elsewhere). sendToAnalytics({ perFrameLayoutShiftData, cumulativeLayoutShiftScore}); } });
レイアウトシフトスコアは一つの指標に過ぎず、「ジャンプ感」というユーザー体験と おおよそ相関があります。
開発者はレイアウトシフトスコア間のわずかな違いを気にしすぎないことが推奨されます。 この指標は高精度の値を意図しているわけではなく、計算効率を優先して ユーザーエージェントが精度を犠牲にすることもあります。 さらに、この指標の定義は今後見直される可能性もあります。
2. 用語
2.1. 基本概念
始点(starting point)は、座標空間
C における
Node
N の始点として次のように定義される:
-
もし N が、1つ以上のボックスを生成する
Elementであれば、C における N の始点は、C の原点から フロー相対の始まりのコーナーまでの ピクセル単位での2次元オフセットであり、 それはNの主ボックスの最初の フラグメントのコーナーを指す。 -
もし N がテキストノード であれば、C における N の始点は ピクセル単位 での原点から フロー相対なコーナーまでの 2次元オフセットであり、それは N によって生成された 最初のラインボックス のコーナーである。
変形非依存始点(transform-indifferent starting point)は、
Node
N の C における
始点を、
すべての変形要素が
変換行列を単位行列とするかのように計算したものとする。
注: ノードがシフトしたかどうかを判断する際には、 トランスフォームを考慮した場合としない場合の始点を比較し、ノードがトランスフォームの変化のみで 不安定になることを防ぐ。ただし、視覚的表現やビューポート外への除外の計算には 常にCSSのトランスフォームが考慮される。
視覚的表現(visual
representation)
は Node
N について以下のように定義される:
-
もし N が1つ以上のボックスを生成する
Elementであれば、 N の視覚的表現は N が生成した全てのフラグメント内に存在する点の集合であり、 これはビューポートの座標空間において ビューポート外の点を除外したものである。 -
もし N がテキストノード であれば、 N の視覚的表現は、N によって生成された全ての ラインボックス の内側にある各点の集合であり、 ビューポート の座標空間で、ビューポートの外の点は除外する。
ある条件が前フレーム時点で(in the previous frame)成立するとき、 それは直近のレイアウトシフト報告アルゴリズムの完了直後の 時点にその条件が真であったことを意味する。
前フレームの始点(previous
frame starting point)は、
座標空間CにおけるNode
Nについて、
前フレーム時点における
始点を指す。
前フレームの変形非依存始点(previous frame
transform-indifferent starting point)は、
座標空間Cにおける
Node
Nについて、
前フレーム時点
での変形非依存始点を指す。
前フレームの視覚的表現(previous frame visual representation)は、
Node
Nについて、
前フレーム時点
での視覚的表現の集合である。
各ユーザーエージェントは、レイアウトシフトと見なすかどうかを判定するのに使われる整数値 意義ありピクセル数(number of pixels to significance)を定義する。 この柔軟性により、ユーザーエージェントはパフォーマンスやユーザー体験を考慮して調整できる。
点Aが点Bと大きく異なる(differs significantly)とは、 AとBが 意義ありピクセル数以上だけ 水平または垂直方向のいずれかで ピクセル単位で異なる場合を指す。
注: Chrome では 意義ありピクセル数 を 3 と定義している。
2.2. 不安定ノード
Node
N は座標空間 C で シフトした(has shifted)とみなされるのは、次の場合である:
それ以外の場合、N は シフトしなかった(has not shifted)とみなされる。
Node
N は 不安定候補(unstable-candidate)であるのは次の場合である:
-
N は次のいずれかである
-
現在および前フレーム時点の computed value の visibility プロパティが "visible" であり、かつ
-
現在および前フレーム時点の computed value の opacity プロパティが N およびすべての先祖で 0 ではない、かつ
-
N はシフトした 状態にあり、その座標空間は 初期コンテインブロックである、かつ
-
次をみたす
ElementP が存在しない:-
現在および前フレーム時点の P が コンテインブロックチェーン上で N の先祖であり、かつ
-
現在および前フレーム時点の P が スクロール可能オーバーフロー領域を持ち、かつ
-
P が不安定候補でなく、かつ
-
N は シフトしなかった 状態であり、その座標空間は P のスクロール可能オーバーフロー領域である。
-
注: スクロール可能オーバーフロー領域に関する条件は、 単なるスクロール操作によってノードが不安定とみなされるのを防ぐためのものです。
Node
N は、
不安定候補であって、
インライン・クリップ・クロッサーでない場合、
不安定(unstable)である。
Node
N は インライン・クリップ・クロッサー(inline clip crosser)であるのは:
-
N が不安定候補であり、
-
N の視覚的表現または前フレームの視覚的表現 のいずれかが空であり、かつ
-
次を仮定した場合 N はもはや不安定候補でなくなる:
— 大きく異なる の定義内の「水平方向または垂直方向」の表現を、(ブロック軸が縦書きの場合は「垂直方向」に、 ブロック軸が横書きの場合は「水平方向」に 置き換える。
注: インライン・クリップ・クロッサーの例としては、インライン方向に クリップ境界をまたぐようにしてビュー内またはビュー外へ移動する要素が挙げられる。 こうした要素はブロックフロー方向にずれない限り、不安定ノード集合から除外される。 これにより「カルーセル」型UIコントロールのようなものを簡単に構築できる。
不安定ノード集合(unstable node
set)は、
Document
D において、
D の不安定な
シャドウを含む子孫すべてを含む集合である。
注: 最初のフレームでは、前フレームの始点が存在しないため、どのノードも不安定ノード集合に含まれない。
2.3. レイアウトシフト値
ビューポート基準距離とは、 ビジュアルビューポートの幅 と ビジュアルビューポートの高さ のうち大きい方である。
移動ベクトルは、Node
Nについて、ピクセル単位での2次元オフセットである。
移動距離は
Node
N について、次のうち大きい方である:
最大移動距離は
Document
D の不安定ノード集合
内のすべてのNode
についての移動距離の最大値、または集合が空の場合は0。
距離率は
Document
Dについて、次の小さい方:
-
D の最大移動距離を ビューポート基準距離 で割った値(ビューポート基準距離が0なら0)、
-
1.0
ノード影響領域は
不安定 なNode
Nに対し、次の点の集合:
-
N の視覚的表現内のすべての点、
-
N の前フレームの視覚的表現内のすべての点。
影響領域は
Document
Dについて、
不安定ノード集合内の
全Node
のノード影響領域中の
すべての点の集合。
影響率は
Document
Dについて、
影響領域の面積を
ビューポートの面積で割った値(ビューポートの面積が0なら0)。
注: 影響領域の面積計算は2次元における Klee measure problemにあたる。 掃引線とセグメントツリーを用いて O(n lg n) 時間で (nは不安定ノード数)、このように解ける。
レイアウトシフト値は
Document
Dについて、
影響率に
距離率を掛けたものとする。
注: レイアウトシフト値は、 レイアウト不安定性がビューポートのどれほどの割合に影響したかと 各要素がどれくらい移動したかの最大値を両方考慮に入れている。 大きな要素が少しだけずれた場合は、ページ全体の不安定感が低い とみなされるケースに対応している。
2.4. 入力除外
除外入力とは、 ドキュメントへのユーザーの積極的な操作を示す入力デバイスからの イベント、またはビューポートのサイズを直接変化させるイベントである。
除外入力には、主に mousedown、 keydown、 pointerdown、 changeイベントなどが含まれる。 ただし、フリックやスクロールジェスチャーの開始や更新のみの効果を持つ イベントは除外入力に該当しない。
ユーザーエージェントは、 pointerdownイベント発生後、 そのイベントがフリックもしくはスクロールの開始でないと判明するまで レイアウトシフトの報告を遅延させてもよい。
mousemove および pointermove イベントも除外入力ではない。
3. LayoutShift
インターフェイス
[Exposed =Window ]interface :LayoutShift PerformanceEntry {readonly attribute double value ;readonly attribute boolean hadRecentInput ;readonly attribute DOMHighResTimeStamp lastInputTime ;readonly attribute FrozenArray <LayoutShiftAttribution >sources ; [Default ]object (); };toJSON
すべての属性値はレイアウトシフトを報告する ステップによって設定される。
sources属性は
FrozenArray
(LayoutShiftAttribution オブジェクトの配列)を返す。
この配列は影響領域の大きい順にソートされ、先頭要素が最もノード影響領域
の大きな要素、すなわちレイアウトシフトに最も寄与した要素を表す。
Layout Instability APIを実装するユーザーエージェントは、
supportedEntryTypes
に
を
Window
コンテキストで必ず含めなければならない。
これにより開発者は Layout Instability API のサポートを検知できる。
4. LayoutShiftAttribution
インターフェイス
[Exposed =Window ]interface {LayoutShiftAttribution readonly attribute Node ?;node readonly attribute DOMRectReadOnly previousRect ;readonly attribute DOMRectReadOnly currentRect ; };
注: previousRect
および currentRect
属性はCSSピクセル単位で長方形を報告し、
getBoundingClientRect()、
IntersectionObserver、
ResizeObserver
など他のWebプラットフォームAPIと同様である。
これにより、レイアウトシフトと他のDOM測定値の相関が容易になるデバイス非依存の座標系が提供される。
各LayoutShiftAttribution
は、そのNode
(対応ノード)と結びついている。
LayoutShiftAttribution
インスタンスAの
node属性のgetterは、
Aの対応ノードと
そのノードドキュメントを入力に、
要素取得アルゴリズムを呼び出し、その結果を返す。
注: 要素取得アルゴリズムにより、 属性づけられたノードが現在接続されていない、もしくはシャドウルート内である場合は node属性がnullになることが保証される。
要素取得アルゴリズムは Element Timing 仕様から 本仕様で再利用しやすい場所に移すべきである。
要素取得アルゴリズムは
Elementではなく
Node
を受け付けるよう一般化すべきである。
previousRect およびcurrentRect の各属性値は帰属を作成するステップによって設定される。
5. 処理モデル
レンダリングの更新 ステップ内で、Layout Instability API を実装するユーザーエージェントは ペイントタイミングをマーク アルゴリズムの実行後、次の処理を行わなければならない:
-
すべての fully active な
Documentについて レイアウトシフトを報告する アルゴリズムを呼ぶ。
5.1. レイアウトシフトを報告する
Document
Dに対して
レイアウトシフトを報告するとき、以下の手順を実行する:
-
現在の D のレイアウトシフト値が0でなければ:
-
D の関連 Realmで 新しい
LayoutShiftオブジェクトnewEntryを作成する。 -
newEntry の
name属性にを設定する。"layout-shift" -
newEntry の
entryType属性にを設定する。"layout-shift" -
newEntry の
startTime属性に、 現在の高解像度時刻 をD の 関連グローバルオブジェクトで取得し設定する。 -
newEntry の
duration属性を0に設定する。 -
newEntry の
value属性に 現在のDのレイアウトシフト値を設定する。 -
newEntry の
lastInputTime属性に、 もっとも最近の 除外入力の時刻(セッションで発生していなければ0)を設定する。 -
newEntry の
hadRecentInput属性を、lastInputTimeの値が直近500ミリ秒未満かどうかでまたはtrue で設定する。false -
newEntry の
sources属性を、D に対して レイアウトシフトの要素情報を報告する アルゴリズムの結果で設定する。 -
PerformanceEntryをキューする 際のオブジェクトとしてnewEntryを用いる。
-
5.2. レイアウトシフト要素情報の報告
Document
D に対し
レイアウトシフト要素情報を報告する
と求められた時、次の手順を実行する:
-
D の不安定ノード集合の各メンバー N について、次の手順を行う:
-
もし N のノード影響領域 が C の任意の要素 existingNode の ノード影響領域 の部分集合であるなら、処理を継続する(continue)。
-
そうでなく、C の任意の要素 existingNode について ノード影響領域 が N のノード影響領域の部分集合であるものがあれば、 その最初の existingNode を C 内で N で 置き換える。
-
それ以外で C の要素数が5未満なら、 CにNを追加する。
注: 「5」という値の選択は任意だが、詳細な要素帰属情報を 提供しつつメモリコストや公開ノードセットの スパム化を抑制するバランスをとっている。
-
それ以外の場合、次を行う:
-
-
Cを降順ソートする。aの ノード影響領域の面積が bの ノード影響領域の面積より小さい場合 aをより小さいとみなす。
注: sources属性は影響領域の大きい順の レイアウトシフト要素情報を公開し、 レイアウトシフトに最も寄与した要素が最初となる。
-
各Cのメンバーごとに 帰属を作成するアルゴリズムを実行して作成した
FrozenArray形式のLayoutShiftAttributionオブジェクト群を返す。
Node
Nに対し
帰属を作成する
と求められた時、次を行う:
-
N の関連 Realm で 新しい
LayoutShiftAttributionオブジェクトAを作成する。 -
A の対応ノードにNを設定する。
-
A の
previousRect属性に、 長方形(Rectangle)のうち 前フレームの視覚的表現 全体を含む最小のものをCSSピクセル 単位で設定する。 -
A の
currentRect属性に、 長方形(Rectangle)のうち 視覚的表現 全体を含む最小のものをCSSピクセル 単位で設定する。 -
Aを返す。
6. セキュリティとプライバシーに関する考慮事項
レイアウト不安定性は リソースタイミング と間接的な関係を持つ。なぜなら遅いリソースがなければ発生しなかった一時的なレイアウトを引き起こす可能性もあるためだ。 リソースタイミング情報は、統計的フィンガープリント 目的で悪意あるウェブサイトに利用されうる。レイアウト不安定性APIは 現在のブラウジングコンテキストの不安定性のみを報告する。複数のブラウジングコンテキストをまたいだ 不安定性スコアの集約情報は直接提供しない。開発者は手動でこのような集約を実装できるが、 オリジンの異なる オリジンの ブラウジングコンテキスト同士は、不安定性スコアを共有するには協調が必要となる。