调整观察器

W3C首次公开工作草案,

此版本:
https://www.w3.org/TR/2020/WD-resize-observer-1-20200211/
最新发布版本:
https://www.w3.org/TR/resize-observer-1/
编辑草案:
https://drafts.csswg.org/resize-observer/
编辑者:
(谷歌)
(微软)
建议对本规范进行编辑:
GitHub编辑器
问题跟踪:
GitHub问题

摘要

该规范描述了一个用于观察元素大小变化的API。

CSS 是一种用于描述结构化文档(如HTML和XML)在屏幕、纸张等上呈现的语言。

本文件状态

本节描述了该文档在发布时的状态。其他文档可能会取代本文件。当前W3C出版物列表及本技术报告的最新版本可在 W3C技术报告索引中找到。

该文档是首次公开工作草案

作为首次公开工作草案的发布并不意味着W3C成员的认可。此文档是草案,可能会随时更新、替换或被其他文档取代。引用此文档为工作进行中的版本是不合适的。

GitHub Issues是讨论此规范的首选方式。提交问题时,请在标题中包含“resize-observer”一词,最好像这样: “[resize-observer] …评论摘要…”。所有问题和评论都被存档, 还有一个历史归档

此文档由CSS工作组生成。

本文件由根据W3C专利政策运作的小组生成。W3C维护着与小组交付物相关的公开专利披露列表;该页面还包括披露专利的说明。拥有专利的个人,如果认为其专利包含必要主张,必须根据W3C专利政策第6节披露信息。

本文件受2019年3月1日W3C流程文档的约束。

1. 介绍

本节是非规范性的。

响应式Web组件需要对Element的大小变化做出响应。一个例子是一个显示地图的Element

响应式Web应用程序已经可以响应视口大小的变化。 这可以通过CSS媒体查询或window.resize事件来完成。

ResizeObserver API是一个用于观察Element大小变化的接口。它是Element的对应物,类似于window.resize事件。

ResizeObserver的通知可用于响应Element大小的变化。关于这些观察的一些有趣事实:

<canvas id="elipse" style="display:block"></canvas>
<div id="menu" style="display:block;width:100px">
    <img src="hamburger.jpg" style="width:24px;height:24px">
    <p class="title">menu title</p>
</div>
// 响应调整大小时,elipse在canvas内绘制一个椭圆
document.querySelector('#elipse').handleResize = entry => {
    entry.target.width = entry.borderBoxSize[0].inlineSize;
    entry.target.height = entry.borderBoxSize[0].blockSize;
    let rx = Math.floor(entry.target.width / 2);
    let ry = Math.floor(entry.target.height / 2);
    let ctx = entry.target.getContext('2d');
    ctx.beginPath();
    ctx.ellipse(rx, ry, rx, ry, 0, 0, 2 * Math.PI);
    ctx.stroke();
}
// 响应调整大小时,根据宽度更改标题可见性
document.querySelector('#menu').handleResize = entry => {
    let title = entry.target.querySelector(".title")
    if (entry.borderBoxSize[0].inlineSize < 40)
        title.style.display = "none";
    else
        title.style.display = "inline-block";
}

var ro = new ResizeObserver( entries => {
  for (let entry of entries) {
    let cs = window.getComputedStyle(entry.target);
    console.log('观察元素:', entry.target);
    console.log(entry.contentRect.top,' 是 ', cs.paddingTop);
    console.log(entry.contentRect.left,' 是 ', cs.paddingLeft);
    console.log(entry.borderBoxSize[0].inlineSize,' 是 ', cs.width);
    console.log(entry.borderBoxSize[0].blockSize,' 是 ', cs.height);
    if (entry.target.handleResize)
        entry.target.handleResize(entry);
  }
});
ro.observe(document.querySelector('#elipse'));
ro.observe(document.querySelector('#menu'));

2. 调整观察器 API

2.1. ResizeObserver 接口

ResizeObserver 接口用于观察Element的大小变化。

它基于MutationObserverIntersectionObserver

enum ResizeObserverBoxOptions {
    "border-box", "content-box", "device-pixel-content-box"
};

ResizeObserver 可以观察不同类型的CSS大小:

device-pixel-content-box可以通过将devicePixelRatio乘以content-box的大小来近似计算。 但由于浏览器特定的子像素对齐行为,作者无法确定正确的舍入方式。 UA如何计算元素的设备像素框是依赖于具体实现的。一种可能的实现是将框的大小和位置乘以设备像素比,然后舍入生成的浮点大小和位置,以最大限度地提高渲染输出的质量。

注意,此大小可能会受到目标位置变化的影响,因此通常比其他大小的观察更昂贵。

dictionary ResizeObserverOptions {
    ResizeObserverBoxOptions box = "content-box";
};

本节是非规范性的。作者可能希望观察多个CSS框。在这种情况下,作者需要使用多个ResizeObserver。

// 观察 content-box
ro.observe(document.querySelector('#menu'), { box: 'content-box' });

// 仅观察边框框。替换之前的观察。
ro1.observe(document.querySelector('#menu'), { box: 'border-box' });

这不会影响在事件触发时返回给定义回调的框尺寸,它仅定义作者希望观察布局变化的框。

[Exposed=(Window),
 Constructor(ResizeObserverCallback callback)]
interface ResizeObserver {
    void observe(Element target, optional ResizeObserverOptions options);
    void unobserve(Element target);
    void disconnect();
};
new ResizeObserver(callback)
  1. this 成为新的 ResizeObserver 对象。

  2. thiscallback 内部槽设置为 callback。

  3. thisobservationTargets 内部槽设置为空列表。

  4. this 添加到 DocumentresizeObservers 槽中。

observe(target, options)

将目标添加到观察元素列表中。

  1. 如果 target 已在 observationTargets 槽中,调用 unobserve(target)。

  2. 创建新的 resizeObservation,使用 ResizeObservation(target, options)。

  3. resizeObservation 添加到 observationTargets 槽中。

unobserve(target)

target 从观察元素列表中移除。

  1. ResizeObservation 中找到目标为 targetobservation

  2. 如果未找到 observation,则返回。

  3. observationTargets 中移除 observation

disconnect()
  1. 清空 observationTargets 列表。

  2. 清空 activeTargets 列表。

2.2. ResizeObserverCallback

callback ResizeObserverCallback = void (sequence<ResizeObserverEntry> entries, ResizeObserver observer);

此回调传递 ResizeObserver 的通知。它由广播活动观察算法调用。

2.3. ResizeObserverEntry

[Exposed=Window]
interface ResizeObserverEntry {
    readonly attribute Element target;
    readonly attribute DOMRectReadOnly contentRect;
    readonly attribute sequence<ResizeObserverSize> borderBoxSize;
    readonly attribute sequence<ResizeObserverSize> contentBoxSize;
    readonly attribute sequence<ResizeObserverSize> devicePixelContentBoxSize;
};

contentRect 来源于 ResizeObserver 的孵化阶段,目前仅出于 Web 兼容性原因而包含。它可能在未来的级别中被弃用。

target, 类型为 Element,只读

大小发生变化的 Element

contentRect, 类型为 DOMRectReadOnly,只读

Element内容矩形,在 ResizeObserverCallback 调用时。

borderBoxSize, 类型为 sequence<ResizeObserverSize>,只读

包含 Element边框盒 大小的序列,在 ResizeObserverCallback 调用时。

contentBoxSize, 类型为 sequence<ResizeObserverSize>,只读

包含 Element内容矩形 大小的序列,在 ResizeObserverCallback 调用时。

devicePixelContentBoxSize, 类型为 sequence<ResizeObserverSize>,只读

包含 Element 的内容矩形大小的序列,以设备像素为单位,在 ResizeObserverCallback 调用时。

盒大小属性以序列的形式暴露,以支持具有多个片段的元素, 这种情况出现在 多列 场景中。 但当前 内容矩形边框盒 的定义并未提及这些盒在 多列 布局中的影响。 在本规范中,序列中只会返回一个 ResizeObserverSize, 该尺寸将对应于第一列的尺寸。 未来版本的此规范将扩展返回的序列,以包含每个片段的大小信息。

interface ResizeObserverSize {
    readonly attribute unrestricted double inlineSize;
    readonly attribute unrestricted double blockSize;
};

3. 处理模型

3.1. ResizeObservation 示例结构

本节为非规范性内容。ResizeObservation 是一个示例结构体,可以用于实现 Resize Observer。它被包含在此处以帮助在处理模型期间提供清晰的说明。它有效地保存了单个 Element 的观察信息。此接口对 Javascript 不可见。

[Constructor(Element target)
]
interface ResizeObservation {
    readonly attribute Element target;
    readonly attribute ResizeObserverBoxOptions observedBox;
    readonly attribute sequence<ResizeObserverSize> lastReportedSizes;
};
target, 类型为 Element,只读

被观察的 Element

observedBox, 类型为 ResizeObserverBoxOptions,只读

正在被观察的盒模型。

lastReportedSizes, 类型为 sequence<ResizeObserverSize>,只读

按顺序记录的上次报告的大小。

new ResizeObservation(target, observedBox)
  1. this 为一个新的 ResizeObservation 对象

  2. this 内部的 target 槽设置为 target

  3. this 内部的 observedBox 槽设置为 observedBox

  4. this 内部的 lastReportedSizes 槽设置为 [(0,0)]

isActive()
  1. 通过 计算盒子大小 并根据 targetobservedBox 设置 currentSize

  2. 如果 currentSize 不等于 lastReportedSizes 中的第一个条目,则返回 true。

  3. 返回 false。

3.2. 内部槽定义

3.2.1. 文档

文档 有一个 resizeObservers 槽,它是该文档中 ResizeObserver 的列表。它初始化为空。

3.2.2. ResizeObserver

ResizeObserver 有一个 callback 槽,由构造函数初始化。

ResizeObserver 有一个 observationTargets 槽,它是 ResizeObservation 的列表。 它代表所有被观察的元素。

ResizeObserver 有一个 activeTargets 槽,它是 ResizeObservation 的列表。它代表自上次观察广播以来尺寸已更改且符合广播条件的所有元素。

ResizeObserver 有一个 skippedTargets 槽,它是 ResizeObservation 的列表。它代表自上次观察广播以来尺寸已更改但符合广播条件的所有元素。

3.3. CSS 定义

3.3.1. 内容矩形

DOM 内容矩形 是一个矩形,其:

内容宽度 规范未提及 多列 布局如何影响内容盒。在本规范中,多列元素 的内容宽度是 getComputedStyle(element).width 的结果。目前,这会计算出第一列的宽度。

将内容矩形的位置设置为内边距顶部/左侧对于目标子元素的绝对定位很有用。绝对定位坐标空间的原点是内边距矩形的左上角。

观察内容矩形意味着:

Web 内容也可以包含 SVG 元素。SVG 元素定义 边界框 而不是内容盒。 SVGGraphicsElement 的内容矩形是一个矩形,其:

3.4. 算法

3.4.1. 按深度收集活动观察

它计算 document 的所有活动观察。要 按深度收集活动观察,请执行以下步骤:

  1. depth 设为传入的深度。

  2. 对于每个 observerresizeObservers 中运行这些步骤:

    1. 清除 observeractiveTargetsskippedTargets

    2. 对于每个 observationobserver.observationTargets 运行此步骤:

      1. 如果 observation.isActive() 为 true

        1. targetDepth 设为 计算节点深度 的结果,针对 observation.target

        2. 如果 targetDepth 大于 depth,则将 observation 添加到 activeTargets

        3. 否则将 observation 添加到 skippedTargets

3.4.2. 是否有活动观察

要确定 Document 是否有活动观察,请运行以下步骤:

  1. 对于每个 observerresizeObservers 中运行此步骤:

    1. 如果 observer.activeTargets 不为空,则返回 true。

  2. 返回 false。

3.4.3. 是否有跳过的观察

要确定 Document 是否有跳过的观察,请运行以下步骤:

  1. 对于每个 observerresizeObservers 中运行此步骤:

    1. 如果 observer.skippedTargets 不为空,则返回 true。

  2. 返回 false。

3.4.4. 创建并填充 ResizeObserverEntry

创建并填充 ResizeObserverEntry,针对给定的 target,请运行以下步骤:
  1. this 设为一个新的 ResizeObserverEntry

  2. this.target 槽设置为 target

  3. this.borderBoxSize 槽设置为基于 计算大小,给定 target 和 "border-box" 的 observedBox 的结果。

  4. this.contentBoxSize 槽设置为基于 计算大小,给定 target 和 "content-box" 的 observedBox 的结果。

  5. this.devicePixelContentBoxSize 槽设置为基于 计算大小,给定 target 和 "device-pixel-content-box" 的 observedBox 的结果。

  6. this.contentRect 设为逻辑上的 this.contentBoxSize,给定 target 和 "content-box" 的 observedBox。

  7. 如果 target 不是 SVG 元素,执行以下步骤:

    1. this.contentRect.top 设为 target.内边距顶部

    2. this.contentRect.left 设为 target.内边距左侧

  8. 如果 target 是 SVG 元素,执行以下步骤:

    1. this.contentRect.top 和 this.contentRect.left 设为 0。

3.4.5. 广播活动观察

广播活动观察 在文档中传递所有活动观察,并返回最浅广播目标深度。

要为 document 广播活动观察,请运行以下步骤:

  1. shallowestTargetDepth 设为 ∞

  2. 对于每个 observerdocument.resizeObservers 中运行这些步骤:

    1. 如果 observer.activeTargets 槽为空,则继续。

    2. entries 设为空的 ResizeObserverEntry 列表。

    3. 对于每个 observationactiveTargets 中,执行以下步骤:

      1. entry 设为运行 创建并填充 ResizeObserverEntry 的结果,针对 observation.target

      2. entry 添加到 entries

      3. observation.lastReportedSizes 设为匹配 entry 的大小。

        1. 匹配的大小是 entry.borderBoxSize,如果 observation.observedBox 为 "border-box"

        2. 匹配的大小是 entry.contentBoxSize,如果 observation.observedBox 为 "content-box"

        3. 匹配的大小是 entry.devicePixelContentBoxSize,如果 observation.observedBox 为 "device-pixel-content-box"

      4. targetDepth 设为 计算节点深度 的结果,针对 observation.target

      5. 如果 targetDepth < shallowestTargetDepth,则将 shallowestTargetDepth 设为 targetDepth

    4. 调用 observer.callback,传递 entries

    5. 清除 observer.activeTargets

  3. 返回 shallowestTargetDepth

3.4.6. 传递 Resize 循环错误

传递 Resize 循环错误通知,请运行以下步骤:

  1. 创建一个新的 ErrorEvent

  2. event 的消息槽初始化为 "ResizeObserver 循环完成但未传递通知"。

  3. 报告异常 event

3.4.7. 计算节点深度

计算节点深度,给定 node,请运行以下步骤:

  1. p 设为从 node 到此元素的扁平 DOM 树的根元素的父级遍历路径。

  2. 返回 p 中的节点数量。

3.4.8. 计算盒子大小,给定目标和观察盒

该算法计算 target Element 的观察盒大小。盒子的类型由 ResizeObserverBoxOptions 描述。 SVG 元素是一个例外。SVG 的大小始终是其边界框大小,因为 SVG 元素不使用标准 CSS 盒模型。

计算盒子大小,给定 targetobservedBox,请运行以下步骤:

  1. 如果 targetSVGGraphicsElement

    1. computedSize.inlineSize 设为 target边界框 行内长度。

    2. computedSize.blockSize 设为 target边界框 块长度。

  2. 如果 target 不是 SVGGraphicsElement

    1. 如果 observedBox 是 "border-box"

      1. computedSize.inlineSize 设为目标的 边框区域 行内长度。

      2. computedSize.blockSize 设为目标的 边框区域 块长度。

    2. 如果 observedBox 是 "content-box"

      1. computedSize.inlineSize 设为目标的 内容区域 行内长度。

      2. computedSize.blockSize 设为目标的 内容区域 块长度。

    3. 如果 observedBox 是 "device-pixel-content-box"

      1. computedSize.inlineSize 设为目标的 内容区域 行内长度,以整设备像素为单位。

      2. computedSize.blockSize 设为目标的 内容区域 块长度,以整设备像素为单位。

    4. 返回 computedSize

3.5. ResizeObserver 生命周期

ResizeObserver 将保持存活,直到满足以下两个条件:

3.6. 外部规范集成

3.6.1. HTML 处理模型:事件循环

ResizeObserver 的处理发生在 HTML 处理模型 事件循环的步骤 7.12 中。

第 12 步目前的说明为:

对于 docs 中的每个完全活动的文档,更新该文档及其浏览上下文的渲染或用户界面,以反映当前状态。

现有的第 12 步可以详细说明为:

对于 docs 中的每个完全活动的文档,运行以下步骤以用于该文档及其浏览内容:

  1. 重新计算样式

  2. 更新布局

  3. 绘制

ResizeObserver 通过循环添加调整大小通知来扩展第 12 步,直到没有待处理的通知为止。这可能导致无限循环。

通过在每次迭代时缩小可以通知的节点集来防止无限循环。在每次迭代中,只有比上一次迭代中最浅节点更深的节点可以发出通知。

如果通知循环完成并且有未传递的通知,则会生成错误。具有未传递通知的元素将在下一个循环中被考虑进行传递。

具有 ResizeObserver 通知的第 12 步为:

对于 docs 中的每个完全活动的文档,运行以下步骤以用于该文档及其浏览上下文:

  1. 重新计算样式

  2. 更新布局

  3. 设置 depth 为 0

  4. 按深度收集活动观察 depth,针对 Document

  5. 当文档 有活动观察 时重复

    1. depth 设为 广播活动观察 的结果。

    2. 重新计算样式

    3. 更新布局

    4. 按深度收集活动观察 depth,针对 Document

  6. 如果 Document 有跳过的观察,则 传递 Resize 循环错误通知

  7. 更新 Document 及其浏览上下文的渲染或用户界面,以反映当前状态。

规范一致性

文档约定

一致性要求通过描述性断言和 RFC 2119 术语的结合表达。规范部分中的关键词“必须”、“禁止”、“需要”、“应”、“不应”、“建议”、“可以”和“可选”应按照 RFC 2119 中的描述进行解释。然而,为了可读性,本规范中这些词并未全部使用大写字母。

本规范的所有文字均为规范性内容,除非明确标记为非规范性、示例或注释的部分。[RFC2119]

本规范中的示例通过“例如”引入,或与规范性文本分开,使用 class="example",如下所示:

这是一个信息性示例的示例。

信息性注释以“注释”一词开头,并与规范性文本分开,使用 class="note",如下所示:

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

建议性部分是规范性部分,旨在引起特别注意,并与其他规范性文本分开,使用 <strong class="advisement">,如下所示: 用户代理必须提供可访问的替代方案。

规范一致性类别

本规范的一致性定义为以下三种一致性类别:

样式表
一个 CSS 样式表
渲染器
一个 用户代理,解释样式表的语义并渲染使用它们的文档。
创作工具
一个 用户代理,用于编写样式表。

如果样式表中使用本模块定义的语法的所有语句都根据通用 CSS 语法和本模块定义的各个特性的语法是有效的,则该样式表符合本规范。

如果渲染器除了按照相关规范解释样式表之外,还通过正确解析并相应地渲染文档来支持本规范定义的所有特性,则该渲染器符合本规范。但是,用户代理因设备限制无法正确渲染文档的情况并不会使用户代理不符合规范。(例如,用户代理不需要在单色显示器上渲染颜色。)

如果创作工具编写的样式表在语法上符合通用 CSS 语法以及本模块中每个特性的语法,并满足本模块中描述的样式表的一致性要求,则该创作工具符合本规范。

负责任的 CSS 实现要求

以下章节定义了实现 CSS 时负责任的多个一致性要求,以促进当前和未来的互操作性。

部分实现

为了使作者能够利用向前兼容的解析规则分配回退值,CSS 渲染器必须将其不支持的任何 at-rules、属性、属性值、关键词及其他语法结构视为无效(并适当地忽略它们)。 特别是,用户代理不得在单个多值属性声明中选择性地忽略不支持的属性值并仅应用支持的值:如果某个值被视为无效(如不支持的值),则 CSS 要求忽略整个声明。

实现不稳定和专有特性

为了避免与未来稳定的 CSS 特性发生冲突,CSS 工作组建议在实现不稳定的特性和专有扩展时,遵循最佳实践

实现 CR 级别特性

一旦规范进入候选推荐阶段,实施者应发布任何 CR 级别特性的无前缀实现,前提是他们可以证明该特性已根据规范正确实现,并应避免公开带有前缀的该特性变体。

为了建立和维护跨实现的 CSS 互操作性,CSS 工作组请求非实验性的 CSS 渲染器在发布无前缀实现之前向 W3C 提交一个实现报告(如有必要,还应提交用于该实现报告的测试用例)。提交给 W3C 的测试用例将由 CSS 工作组进行审核和修正。

有关提交测试用例和实现报告的更多信息,请访问 CSS 工作组网站 https://www.w3.org/Style/CSS/Test/。 如有疑问,请发送邮件至 public-css-testsuite@w3.org 邮件列表。

索引

本规范定义的术语

引用定义的术语

参考文献

规范性引用

[CSS-BOX-3]
Elika Etemad. CSS Box Model Module Level 3. 2018年12月18日. WD. URL: https://www.w3.org/TR/css-box-3/
[CSSOM-VIEW-1]
Simon Pieters. CSSOM View Module. 2016年3月17日. WD. URL: https://www.w3.org/TR/cssom-view-1/
[DOM]
Anne van Kesteren. DOM 标准. 现行标准. URL: https://dom.spec.whatwg.org/
[GEOMETRY-1]
Simon Pieters; Chris Harrelson. 几何接口模块一级. 2018年12月4日. CR. URL: https://www.w3.org/TR/geometry-1/
[HTML]
Anne van Kesteren 等. HTML 标准. 现行标准. URL: https://html.spec.whatwg.org/multipage/
[RFC2119]
S. Bradner. 在RFC中用于指示要求级别的关键词. 1997年3月. 最佳当前实践. URL: https://tools.ietf.org/html/rfc2119
[SVG2]
Amelia Bellamy-Royds 等. 可扩展矢量图形 (SVG) 2. 2018年10月4日. CR. URL: https://www.w3.org/TR/SVG2/
[WebIDL]
Boris Zbarsky. Web IDL. 2016年12月15日. ED. URL: https://heycam.github.io/webidl/

IDL 索引

enum ResizeObserverBoxOptions {
    "border-box", "content-box", "device-pixel-content-box"
};

dictionary ResizeObserverOptions {
    ResizeObserverBoxOptions box = "content-box";
};

[Exposed=(Window),
 Constructor(ResizeObserverCallback callback)]
interface ResizeObserver {
    void observe(Element target, optional ResizeObserverOptions options);
    void unobserve(Element target);
    void disconnect();
};

callback ResizeObserverCallback = void (sequence<ResizeObserverEntry> entries, ResizeObserver observer);

[Exposed=Window]
interface ResizeObserverEntry {
    readonly attribute Element target;
    readonly attribute DOMRectReadOnly contentRect;
    readonly attribute sequence<ResizeObserverSize> borderBoxSize;
    readonly attribute sequence<ResizeObserverSize> contentBoxSize;
    readonly attribute sequence<ResizeObserverSize> devicePixelContentBoxSize;
};

interface ResizeObserverSize {
    readonly attribute unrestricted double inlineSize;
    readonly attribute unrestricted double blockSize;
};

[Constructor(Element target)
]
interface ResizeObservation {
    readonly attribute Element target;
    readonly attribute ResizeObserverBoxOptions observedBox;
    readonly attribute sequence<ResizeObserverSize> lastReportedSizes;
};