布局不稳定性 API

社区组报告草案,

本版本:
https://wicg.github.io/layout-instability
问题跟踪:
GitHub
规范内联
编辑:
Google
Google
Google

摘要

本文档定义了一个 API,可基于页面上元素的移动情况,为网页作者提供关于其页面稳定性的洞察。

本文档状态

本规范由 Web 平台孵化器社区组 发布。 它不是 W3C 标准,也不在 W3C 标准轨道上。 请注意,根据 W3C 社区贡献者许可协议 (CLA) 存在有限的退出选择,且适用其他条件。 了解更多信息,请参阅 W3C 社区与业务组

1. 引言

本节为非规范性内容。

网页中 DOM 元素的移动会影响用户体验, 并且在当今网络上非常常见。这种移动通常是由于内容 异步加载并挤占了页面上的其他元素。

布局不稳定性 API 通过为用户会话中的每一帧动画报告一个值 (“布局偏移”)来识别这些不稳定页面。 本规范提供了用户代理计算布局偏移值的方法。

布局偏移值预计与特定时刻布局不稳定的严重程度 大致相关。其计算方法同时考虑了受影响区域的面积及 页面元素被移动的距离。

本规范暴露的这些值不建议用作 “布局变化观察器”,原因有几点。首先,它们与 PerformanceObserver 相关,因此如果用户代理认为影响到 站点性能,则可以延迟分发回调。其次,用户代理可能会忽略 很小的布局偏移。因此,不建议依赖此 API 来运行任何会影响网页用户可见行为的 JavaScript。

1.1. 累积布局偏移(CLS)

本节为非规范性内容。

布局偏移值 代表某一时刻,但如果能有个值代表 用户在页面上停留期间页面的总体不稳定性也很有用。

为此我们提出两个值,用户代理或开发者可以计算 以获得该种表示。(这些定义为非规范性内容, 因为 API 并不暴露这些值。)

累计布局偏移分数预计与页面生命周期内 布局不稳定的严重程度大致相关。

开发者可以用这个 API 计算 DCLS 或 CLS 分数, 方法是在这些值报告到观察器后进行累加, 并在 visibilitychange 事件发生时取得“最终”分数。

该策略已在用例示例中说明。

1.2. 来源归因

本节为非规范性内容。

除了布局偏移值,API 还会报告 最多五个 DOM 元素的采样,这些元素的布局偏移 对当前动画帧的布局偏移值贡献最大。 sources 按影响区域从大到小排序,因此第一个元素表示 对布局偏移贡献最大者。

有时,不稳定的真正“根本原因”可能只是 间接与发生布局偏移的 DOM 元素相关。 例如,当新插入的元素挤下了下方的内容时, sources 属性只会报告被移动的元素,而不会报告插入的元素。

我们认为,用户代理要在间接程度上分析 不稳定的成因,实现有意义的“根本原因”归因是不可行的。 但我们认为,这个 API 提供的针对被偏移元素的直接报告, 对于需要诊断布局不稳定现象的开发者,依然大有价值。

1.3. 用例示例

本节为非规范性内容。

let perFrameLayoutShiftData = [];
let cumulativeLayoutShiftScore = 0;

function updateCLS(entries) {
  for (const entry of 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. 基本概念

起始点Node N 在坐标空间 C 中的起始点定义如下:

与变换无关的起始点Node N 在坐标空间 C 的与变换无关的起始点是: 计算 NC起始点,假定任何 发生变换的元素变换矩阵 都为单位矩阵。

注: 判断节点是否发生偏移时, 我们会同时判断有无变换,使节点不会因变换被视为不稳定。 但 CSS 变换始终会被纳入可视表现的计算,以及视口外点的排除。

可视表现Node N 的可视表现定义如下:

若某条件在前一帧内成立(in the previous frame),即它在最近一次 报告布局偏移算法完成后即时成立。

前一帧起始点Node N 在坐标空间 C 的前一帧起始点是: 它在前一帧NC起始点

前一帧与变换无关的起始点Node N 在坐标空间 C 的前一帧与变换无关的起始点是, 它在前一帧NC与变换无关的起始点

前一帧可视表现Node N 的前一帧可视表现为它在前一帧内 的 可视表现

每个用户代理定义一个显著像素数 (整数),用于计算某次移动是否构成布局偏移。 这样用户代理可以为性能或体验做调整。

若点 A 和点 B 的水平或垂直方向差异达到或超过 显著像素数像素单位,则 A 显著不同B

注: Chrome 将 显著像素数 设为 3。

2.2. 不稳定节点

Node N 满足以下条件,则认为其在坐标空间 C发生了偏移

否则,认为 N 未发生偏移C

Node N 满足下列所有条件,则其为不稳定候选节点

注: 有关可滚动溢出区域的条件旨在防止节点仅因滚动操作而被视为不稳定。

Node N不稳定候选节点 且不是 行内裁剪穿越者, 则称 N不稳定

Node N 若满足下列所有条件,则为行内裁剪穿越者

注: 行内裁剪穿越者示例:一个元素通过行内方向的移动穿过其包含裁剪区域的边界从而进入或离开可视区域。只要这种元素没有在块流方向发生偏移,就不会被算作不稳定节点。这便于特定类型的“轮播”UI 组件实现。

不稳定节点集Document D 的不稳定节点集即其所有 不稳定 包含 Shadow DOM 的所有后代 组成的集合。

注: 在首帧时,没有任何节点的前一帧起始点,因此不稳定节点集为空。

2.3. 布局偏移值

视口基准距离可视视口宽度可视视口高度 的较大值。

移动向量Node N 的移动向量是 从

的二维像素单位的偏移量。

移动距离Node N 的移动距离是

中的较大值。

最大移动距离Document D 的最大移动距离是其 不稳定节点集中 所有 Node移动距离的最大值。 如果 不稳定节点集为空,则为 0。

距离分数Document D 的距离分数为以下两者中较小的那个:

节点影响区域:某个 不稳定 Node N 的节点影响区域是包含下列内容的集合:

影响区域Document D 的影响区域为其 不稳定节点集 中所有 Node节点影响区域包含的所有点的集合。

影响分数Document D 的影响分数为其 影响区域 的面积除以 视口的面积(或若 视口区域面积为 0,则为 0)。

注: 影响区域面积的计算本质上是二维 Klee 测度问题。一种使用扫描线和线段树、 对 n 个不稳定节点时间复杂度为 O(n log n) 的解法见这里

布局偏移值Document D 的布局偏移值为其 影响分数 乘以 距离分数

注: 布局偏移值同时考虑了受影响的视口比例和 任一元素实际移动的最大距离。这样就能反映,即使一个很大的元素仅 移动较小的距离,对页面不稳定性的感知影响也较低。

2.4. 输入排除

排除性输入是指 源自输入设备的、表明用户主动与文档交互的事件,或任何 直接改变视口大小的事件。

排除性输入通常包括 mousedownkeydownpointerdown,以及 change events。 但事件仅用于开始或更新轻拂、滚动手势的,不能视为排除性输入。

用户代理可在 pointerdown 事件之后 延迟布局偏移的上报,直到确认该事件未触发轻拂或滚动手势。

mousemovepointermove 事件也不是排除性输入。

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 对象的数组,按影响区域降序 排列。第一个元素的 节点影响区域最大,代表对布局偏移贡献最大的元素。

实现布局不稳定性 API 的用户代理,必须在 Window 上的 supportedEntryTypes 里包含 "layout-shift", 以便开发者检测此 API 的支持。

4. LayoutShiftAttribution 接口

[Exposed=Window]
interface LayoutShiftAttribution {
  readonly attribute Node? node;
  readonly attribute DOMRectReadOnly previousRect;
  readonly attribute DOMRectReadOnly currentRect;
};

注: previousRectcurrentRect 属性报告的矩形以 CSS 像素为单位,这与 getBoundingClientRect()IntersectionObserverResizeObserver 等其他 Web 平台 API 保持一致。这提供了一套与设备无关的坐标体系,方便将布局偏移测量与其他 DOM 测量关联。

每个 LayoutShiftAttribution 都关联有一个 Node (即其 关联节点)。

获取 LayoutShiftAttribution 实例 Anode 属性时,将 A关联节点 和该节点的 节点 document 作为输入,调用 获取元素(get an element) 算法,并返回其结果。

注: 使用 获取元素算法 可确保若所归因节点已断开连接或在 shadow root 内,node 属性为 null。

获取元素 算法应从 Element Timing 规范中移到更适合本规范复用的位置。

获取元素 算法应泛化为接受 Node 而不仅仅是 Element

previousRectcurrentRect 属性的值由创建归因步骤赋值。

5. 处理模型

更新渲染步骤(属于 事件循环处理模型)期间, 实现布局不稳定性 API 的用户代理在调用 标记绘制时间 算法后,必须执行如下步骤:

  1. 对于 docs 中的每一个完全激活的 Document, 调用报告布局偏移算法, 针对该 Document

5.1. 报告布局偏移

当被要求为激活的 Document D 报告布局偏移时,按如下步骤执行:
  1. 如果 D 当前的 布局偏移值不为 0:

    1. D关联 realm 创建一个 LayoutShift 对象 newEntry

    2. newEntryname 属性设为 "layout-shift"

    3. newEntryentryType 属性设为 "layout-shift"

    4. newEntrystartTime 属性设为 以 D关联全局对象 为输入计算得到的 当前高分辨率时间

    5. newEntryduration 属性设为 0。

    6. newEntryvalue 属性设为 D 当前的 布局偏移值

    7. newEntrylastInputTime 属性设为最近一次 排除性输入 的时间,如果会话期间没有发生过排除性输入,则为 0。

    8. newEntryhadRecentInput 属性设为 true,如果 lastInputTime 少于 500 毫秒前,否则为 false

    9. newEntrysources 属性 设为调用 报告布局偏移来源 算法针对 D 返回的结果。

    10. 将 PerformanceEntry newEntry 对象入队。

5.2. 报告布局偏移来源

当被要求为激活的 Document D 报告布局偏移来源时,按如下步骤执行:
  1. C 为一个空的 列表,列表元素为 Node 对象。

  2. 对于 D不稳定节点集中的每一个成员 N,执行如下步骤:

    1. 如果存在 C 中的任何成员 existingNode,使得 N节点影响区域existingNode节点影响区域 的子集,则继续。

    2. 否则,如果存在 C 中的任何成员 existingNode,使得 existingNode节点影响区域N节点影响区域 的子集,则用 N 替换 C 中第一个此类 existingNode

    3. 否则,如果 C 的成员数量少于 5,则将 N 添加到 C

      注: 选取 5 只是折中,既能提供较详细的归因,又不会导致过多内存消费,也避免暴露节点集时过于冗长。

    4. 否则,执行如下步骤:

      1. smallestC 中面积最小的 节点影响区域的第一个成员。

      2. 如果 N节点影响区域的面积大于 smallest节点影响区域的面积,则用 N 替换 C 中的 smallest

  3. C 按降序排序。若 a节点影响区域面积小于 b节点影响区域面积,则 a 小于 b

    注: 这样可以确保 sources 属性按影响区域降序暴露布局偏移来源,贡献最大者排在首位。

  4. C 中每个成员调用一次 创建归因算法,返回 FrozenArray 类型的 LayoutShiftAttribution 对象数组。

当被要求为 Node N 创建归因时,执行如下步骤:
  1. N关联 realm 创建一个新的 LayoutShiftAttribution 对象 A

  2. A关联节点设为 N

  3. ApreviousRect 属性 设为包含 N 前一帧可视表现的最小 Rectangle,单位为 CSS 像素

  4. AcurrentRect 属性 设为包含 N 可视表现的最小 Rectangle,单位为 CSS 像素

  5. 返回 A

6. 安全性与隐私考虑

布局不稳定性与 资源计时有间接联系, 因为资源加载缓慢可能导致页面出现本不必要的中间布局。资源计时信息可被恶意站点 用于统计指纹识别。布局不稳定性 API 仅报告当前浏览上下文的 不稳定性。它不会直接聚合多个浏览上下文的得分。开发者可手动实现聚合, 但不同的浏览上下文须协作 才能共享不稳定性得分。

一致性

文档约定

一致性要求由描述性断言和 RFC 2119 术语共同表达。 在本规范规范性部分出现的关键词:“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”、“OPTIONAL” 应按 RFC 2119 的说明进行解释。 但为可读性考虑,本文档未将这些词全部大写。

除明确标记为非规范性的章节、示例和注释外, 本规范其余文本均为规范性内容。[RFC2119]

本规范中的示例以“For example”为开头,或者通过 class="example" 标记与规范性内容区分,格式如下:

这是一个信息性示例。

信息性注释以“Note”起头,并用 class="note" 标记与规范性内容区分,格式如下:

注,这是一条信息性注释。

索引

本规范定义的术语

引用定义的术语

参考文献

规范性引用

[CSS-BREAK-4]
Rossen Atanassov; Elika Etemad. CSS Fragmentation Module Level 4. URL: https://drafts.csswg.org/css-break-4/
[CSS-CASCADE-5]
Elika Etemad; Miriam Suzanne; Tab Atkins Jr.. CSS Cascading and Inheritance Level 5. URL: https://drafts.csswg.org/css-cascade-5/
[CSS-COLOR-3]
Tantek Çelik; Chris Lilley; David Baron. CSS Color Module Level 3. URL: https://drafts.csswg.org/css-color-3/
[CSS-DISPLAY-4]
Elika Etemad; Tab Atkins Jr.. CSS Display Module Level 4. URL: https://drafts.csswg.org/css-display-4/
[CSS-OVERFLOW-3]
Elika Etemad; Florian Rivoal. CSS Overflow Module Level 3. URL: https://drafts.csswg.org/css-overflow-3/
[CSS-TEXT-3]
Elika Etemad; Koji Ishii; Florian Rivoal. CSS Text Module Level 3. URL: https://drafts.csswg.org/css-text-3/
[CSS-TRANSFORMS-1]
Simon Fraser; et al. CSS Transforms Module Level 1. URL: https://drafts.csswg.org/css-transforms/
[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 4. URL: https://drafts.csswg.org/css-values-4/
[CSS-WRITING-MODES-4]
Elika Etemad; Koji Ishii. CSS Writing Modes Level 4. URL: https://drafts.csswg.org/css-writing-modes-4/
[CSS2]
Bert Bos; et al. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification. URL: https://drafts.csswg.org/css2/
[CSSOM-VIEW-1]
Simon Fraser; Emilio Cobos Álvarez. CSSOM View Module. URL: https://drafts.csswg.org/cssom-view/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ELEMENT-TIMING]
Element Timing API. Editor's Draft. URL: https://w3c.github.io/element-timing/
[GEOMETRY-1]
Simon Pieters; Chris Harrelson. Geometry Interfaces Module Level 1. URL: https://drafts.fxtf.org/geometry/
[HR-TIME-2]
Ilya Grigorik. High Resolution Time Level 2. URL: https://w3c.github.io/hr-time/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[PAINT-TIMING]
Ian Clelland; Noam Rosenthal. Paint Timing. URL: https://w3c.github.io/paint-timing/
[PERFORMANCE-TIMELINE]
Nicolas Pena Moreno. Performance Timeline. URL: https://w3c.github.io/performance-timeline/
[RESOURCE-TIMING]
Yoav Weiss; Noam Rosenthal. Resource Timing. URL: https://w3c.github.io/resource-timing/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. 最佳当前实践. URL: https://datatracker.ietf.org/doc/html/rfc2119
[VISUAL-VIEWPORT]
Visual Viewport API. cg-draft. URL: https://wicg.github.io/visual-viewport/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

资料性引用

[INTERSECTION-OBSERVER]
Stefan Zager; Emilio Cobos Álvarez; Traian Captan. Intersection Observer. URL: https://w3c.github.io/IntersectionObserver/
[RESIZE-OBSERVER-1]
Aleks Totic; Greg Whitworth. Resize Observer. URL: https://drafts.csswg.org/resize-observer/

IDL 索引

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

[Exposed=Window]
interface LayoutShiftAttribution {
  readonly attribute Node? node;
  readonly attribute DOMRectReadOnly previousRect;
  readonly attribute DOMRectReadOnly currentRect;
};

问题索引

get an element 算法应从 Element Timing 规范中移到更适合本规范复用的位置。
get an element 算法应泛化为能接收 Node 而不仅仅是 Element