1. 介绍
本节非规范性内容。
本规范描述了一种 API,允许开发人员根据计算的样式和盒树的变化来布局一个盒模型。
2. 布局 API 容器
为替代值新增了一种<display-inside>属性:
layout(<ident>)
。
- layout()
- 此值会使元素生成一个布局 API 容器盒模型。
布局 API 容器是元素通过<display-inside>的计算值为layout()生成的盒模型。
一个布局 API 容器为其内容建立一个新的布局 API 格式化上下文。这与建立块格式化上下文相同,但使用作者提供的布局而不是块布局。 例如,浮动不会进入布局 API 容器,布局 API 容器的边距不会与其内容的边距重叠。
布局 API 容器的所有流入子元素称为布局 API 子元素,并使用作者定义的布局进行布局。
布局 API 容器为其内容形成一个包含块,与块容器完全相同。[CSS21]
注意: 在规范的未来版本中,可能会有方法来覆盖包含块的行为。
overflow 属性适用于布局 API 容器。详情见 §4.3 Overflow。
由于布局完全由作者决定,其他布局模式(例如 flex、block)中使用的属性可能不适用。例如,作者可能不会遵循子元素上的边距属性。
2.1. 布局 API 容器绘制
布局 API
容器的子元素绘制方式与内联块[CSS21]完全相同,除了布局方法返回的顺序(通过childFragments
)
替代了原始文档顺序,且z-index
的非auto值即使position是static时,也会创建一个层叠上下文。
2.2. 盒树转换
布局 API 子元素可以根据布局选项的值以不同方式表现,childDisplay
由类上的layoutOptions
设置。
如果布局选项的childDisplay
的值为"block"
,则该子元素的display值将被块化。这类似于弹性容器或网格容器的子元素。
参见[css3-display]。
如果布局选项的childDisplay
的值为"normal"
,则不会发生块化。
相反,具有<display-outside>计算值为内联的子元素(根内联盒)
在调用layoutNextFragment()
时,将为每一行生成一个布局片段
。
注意: 这允许作者调整每一行的可用内联大小,并单独定位每一行。
在上述两种情况下,子元素都会变成原子内联。
注意: 当用户代理遇到块级盒子时,不会执行任何“内联拆分”或分片。
布局子元素
,
同时“block”和“float”将成为原子内联。
<span id="inline-span"> Text <div id="block"></div> <div id="float"></div> Text </span>
3. 布局 API 模型和术语
本节概述了提供给作者的布局 API。
当前布局是我们正在为其执行布局的盒模型的布局算法。
父布局是盒模型的直接父元素的布局算法(该布局算法正在请求执行当前布局)。
子布局是LayoutChild
的布局算法,属于当前布局的子元素。
3.1. 布局子元素
[Exposed=LayoutWorklet] interfaceLayoutChild
{ readonly attribute StylePropertyMapReadOnly styleMap; IntrinsicSizesRequest intrinsicSizes(); LayoutFragmentRequest layoutNextFragment(LayoutConstraintsconstraints
, ChildBreakTokenbreakToken
); };
LayoutChild
包含以下内部槽位:
-
[[box]]
一个 CSS 盒模型。 -
[[styleMap]]
一个StylePropertyMapReadOnly
,这是该子元素的计算样式,包含在childInputProperties
中列出的属性。
LayoutChild
表示一个
CSS 生成的盒模型,在布局发生之前(这些盒模型的计算值都不是display为none)。
LayoutChild
本身不包含任何布局信息(如内联或块大小),但可以用来生成包含布局信息的LayoutFragment
。
作者不能通过此 API 构造LayoutChild
,这是在渲染引擎的独立阶段(样式解析后)完成的。
LayoutChild
可以由以下元素生成:
-
一个元素。
-
一个根内联盒。
-
注意: 其他伪元素,如::first-letter或::first-line不会为布局目的生成
LayoutChild
。它们是文本节点的附加样式信息。 -
一个匿名盒。例如,可能插入匿名盒的情况包括:
-
一个具有display: table-cell的元素,其父元素没有设置display: table。
LayoutChild
中:
<style> #box::before { content: 'hello!'; } </style> <div id="box">A block level box with text.</div> <img src="..." />
LayoutChild
中,因为它们共享一个根内联盒:
This is a next node, <span>with some additional styling, that may</span> break over<br>multiple lines.
多个非原子内联的内容将被放置在同一个LayoutChild
中,以允许渲染引擎跨元素边界执行文本排版。
一个LayoutChild
数组被传递到布局方法中,表示正在布局的当前盒子的子元素。
styleMap
时,用户代理必须执行以下步骤:
-
返回this的
StylePropertyMapReadOnly
,该值包含在[[styleMap]]
内部槽中。
LayoutChild
上调用layoutNextFragment(constraints, breakToken)
方法时,用户代理必须执行以下步骤:
-
创建一个新的
LayoutFragmentRequest
,其内部槽位为:-
[[layoutChild]]
设置为this。 -
[[layoutConstraints]]
设置为constraints。 -
[[breakToken]]
设置为breakToken。
-
-
返回request。
LayoutChild
上调用intrinsicSizes()
方法时,用户代理必须执行以下步骤:
-
创建一个新的
IntrinsicSizesRequest
,其内部槽位为:-
[[layoutChild]]
设置为this。
-
-
返回request。
注意: layoutNextFragment()
和intrinsicSizes()
不会同步运行。有关完整描述,请参见§5.5.1 请求对象。
3.1.1. 布局子元素与盒树
每个盒模型都有一个[[layoutChildMap]]
内部槽位,这是一个LayoutWorkletGlobalScope
到LayoutChild
的有序映射。
-
确认以下内容:
-
将layoutChildMap设置为box的
[[layoutChildMap]]
。 -
如果layoutChildMap[workletGlobalScope]不存在,执行以下步骤:
-
通过给定的name和workletGlobalScope,获取布局定义。
确认获取布局定义成功,且definition不是
"invalid"
。 -
将childInputProperties设置为definition的子输入属性。
-
创建一个新的
LayoutChild
,其内部槽位为:-
[[box]]
设置为box。 -
[[styleMap]]
设置为一个新的StylePropertyMapReadOnly
,并仅包含childInputProperties中列出的计算值。
-
-
将layoutChildMap[workletGlobalScope]设置为layoutChild。
-
-
返回layoutChildMap[workletGlobalScope]的获取结果。
当一个盒模型插入到盒树中时,用户代理可以为所有LayoutWorkletGlobalScope
预填充[[layoutChildMap]]
。
当一个盒模型从盒树中移除时,用户代理必须清除[[layoutChildMap]]
。
-
确认以下内容:
-
box当前已附加到盒树中。
-
-
将layoutChildMap设置为box的
[[layoutChildMap]]
。 -
对 layoutChildMap中的每个layoutChild进行以下操作:
-
将styleMap设置为layoutChild的
[[styleMap]]
。 -
根据box的新计算样式更新styleMap的声明。
-
当盒模型的计算样式发生变化时,用户代理必须运行更新布局子元素样式算法。
3.2. 布局片段
[Exposed=LayoutWorklet] interfaceLayoutFragment
{ readonly attribute doubleinlineSize
; readonly attribute doubleblockSize
; attribute doubleinlineOffset
; attribute doubleblockOffset
; readonly attribute anydata
; readonly attribute ChildBreakToken?breakToken
; };
LayoutFragment
具有以下内部槽位:
-
[[layoutFragmentRequest]]
一个LayoutFragmentRequest
,即生成该片段的请求。 -
[[generator]]
生成该片段的生成器。
LayoutFragment
表示一个
CSS 片段,该片段是在LayoutChild
上执行布局后生成的。这是由layoutNextFragment()
方法生成的。
LayoutFragment
具有inlineSize
和blockSize
属性,这些属性由相应子元素的布局算法设置。它们表示该
CSS 片段的边框盒大小,且相对于当前布局的书写模式。
inlineSize
和blockSize
属性不可更改。如果当前布局需要不同的inlineSize
或blockSize
,则作者必须再次执行layoutNextFragment()
,并使用不同的参数来获得不同的结果。
当前布局中的作者可以通过设置其inlineOffset
和blockOffset
属性来定位生成的LayoutFragment
。如果作者未设置,它们默认值为零。inlineOffset
和blockOffset
属性表示LayoutFragment
相对于其父元素边框盒的位置,未应用变换或定位(例如,如果片段是相对定位的情况)。
registerLayout('block-like', class { *intrinsicSizes(children, edges, styleMap) { const childrenSizes = yield children.map((child) => { return child.intrinsicSizes(); }); const maxContentSize = childrenSizes.reduce((max, childSizes) => { return Math.max(max, childSizes.maxContentSize); }, 0) + edges.all.inline; const minContentSize = childrenSizes.reduce((max, childSizes) => { return Math.max(max, childSizes.minContentSize); }, 0) + edges.all.inline; return {maxContentSize, minContentSize}; } *layout(children, edges, constraints, styleMap) { const availableInlineSize = constraints.fixedInlineSize - edges.all.inline; const availableBlockSize = (constraints.fixedBlockSize || Infinity) - edges.all.block; const childFragments = []; const childConstraints = { availableInlineSize, availableBlockSize }; const childFragments = yield children.map((child) => { return child.layoutNextFragment(childConstraints); }); let blockOffset = edges.all.blockStart; for (let fragment of childFragments) { // 按块状方式排列片段,并在内联方向居中。 fragment.blockOffset = blockOffset; fragment.inlineOffset = Math.max( edges.all.inlineStart, (availableInlineSize - fragment.inlineSize) / 2); blockOffset += fragment.blockSize; } const autoBlockSize = blockOffset + edges.all.blockEnd; return { autoBlockSize, childFragments, }; } });
布局 API
容器可以通过data
属性与其他布局 API
容器通信。该属性由data
成员在FragmentResultOptions
字典中设置。
LayoutFragment
的breakToken
指定了LayoutChild
最后一次分割的位置。如果breakToken
为null,则LayoutChild
不会为该token链生成更多LayoutFragment
。breakToken
可以传递给layoutNextFragment()
函数以为特定子元素生成下一个LayoutFragment
。breakToken
不可更改。如果当前布局需要不同的breakToken
,作者必须再次执行layoutNextFragment()
,并使用不同的参数。
-
将targetRealm设置为generator的域。
-
创建一个新的
LayoutFragment
,其包含:-
[[layoutFragmentRequest]]
内部槽位设置为layoutFragmentRequest。 -
[[generator]]
内部槽位设置为generator。 -
inlineSize
设置为internalFragment相对于当前布局书写模式的内联大小。 -
inlineOffset
初始值为0。 -
blockOffset
初始值为0。 -
breakToken
设置为一个新的ChildBreakToken
,表示internalFragment的内部断点标记(如果有的话)。 -
如果internalFragment中存储了一个clonedData对象,则将
data
设置为StructuredDeserialize(clonedData,targetRealm)的结果,否则为null。
-
-
返回fragment。
3.3. 固有尺寸
[Exposed=LayoutWorklet] interfaceIntrinsicSizes
{ readonly attribute doubleminContentSize
; readonly attribute doublemaxContentSize
; };
IntrinsicSizes
对象具有以下内部槽位:
-
[[intrinsicSizesRequest]]
一个IntrinsicSizesRequest
,即生成这些固有尺寸的请求。
IntrinsicSizes
对象表示一个
CSS 盒模型的最小内容尺寸和最大内容尺寸。它具有minContentSize
和maxContentSize
属性,这些属性表示当前布局中LayoutChild
的边框盒最小/最大内容贡献。这些属性相对于当前布局的书写模式的内联方向。
minContentSize
和maxContentSize
属性不可更改。在当前布局过程中,LayoutChild
的这些值不能更改。
<style> .child-0 { width: 380px; border: solid 10px; } .child-1 { border: solid 5px; } .box { display: layout(intrinsic-sizes-example); font: 25px/1 Ahem; } </style> <div class="box"> <div class="child-0"></div> <div class="child-1">XXX XXXX</div> </div>
registerLayout('intrinsic-sizes-example', class { *intrinsicSizes(children, edges, styleMap) { const childrenSizes = yield children.map((child) => { return child.intrinsicSizes(); }); childrenSizes[0].minContentSize; // 400, (380+10+10) child has a fixed size. childrenSizes[0].maxContentSize; // 400, (380+10+10) child has a fixed size. childrenSizes[1].minContentSize; // 100, size of "XXXX". childrenSizes[1].maxContentSize; // 200, size of "XXX XXXX". } *layout() {} });
-
创建一个新的intrinsicSizes
IntrinsicSizes
对象,包含:-
[[intrinsicSizesRequest]]
内部槽位设置为intrinsicSizesRequest。 -
minContentSize
设置为internalIntrinsicSizes的边框盒最小内容贡献,相对于当前布局的书写模式。 -
maxContentSize
设置为internalIntrinsicSizes的边框盒最大内容贡献,相对于当前布局的书写模式。
-
-
返回intrinsicSizes。
3.4. 布局约束
[Constructor
(optional LayoutConstraintsOptionsoptions
),Exposed=LayoutWorklet] interfaceLayoutConstraints
{ readonly attribute doubleavailableInlineSize
; readonly attribute doubleavailableBlockSize
; readonly attribute double?fixedInlineSize
; readonly attribute double?fixedBlockSize
; readonly attribute doublepercentageInlineSize
; readonly attribute doublepercentageBlockSize
; readonly attribute double?blockFragmentationOffset
; readonly attribute BlockFragmentationTypeblockFragmentationType
; readonly attribute anydata
; }; dictionaryLayoutConstraintsOptions
{ doubleavailableInlineSize
= 0; doubleavailableBlockSize
= 0; doublefixedInlineSize
; doublefixedBlockSize
; doublepercentageInlineSize
; doublepercentageBlockSize
; doubleblockFragmentationOffset
; BlockFragmentationTypeblockFragmentationType
= "none"; anydata
; }; enumBlockFragmentationType
{"none"
,"page"
,"column"
,"region"
};
LayoutConstraints
对象被传递给布局方法,表示用于当前布局进行内部布局的所有约束条件。它还用于将可用空间的信息传递给子布局。
LayoutConstraints
对象具有availableInlineSize
和availableBlockSize
属性。这表示布局中应遵守的可用空间,以生成LayoutFragment
。
注意: 某些布局可能需要生成超出此尺寸的LayoutFragment
。例如,一个替换元素。父布局应预期这种情况并适当地处理。
父布局可能要求当前布局必须具有特定的尺寸。如果指定了fixedInlineSize
或fixedBlockSize
,当前布局应生成具有指定方向上尺寸的LayoutFragment
。
registerLayout('flex-distribution-like', class { *intrinsicSizes(children, edges, styleMap) { const childrenSizes = yield children.map((child) => { return child.intrinsicSizes(); }); const maxContentSize = childrenSizes.reduce((sum, childSizes) => { return sum + childSizes.maxContentSize; }, 0) + edges.all.inline; const minContentSize = childrenSizes.reduce((max, childSizes) => { return sum + childSizes.minContentSize; }, 0) + edges.all.inline; return {maxContentSize, minContentSize}; } *layout(children, edges, constraints, styleMap) { const availableInlineSize = constraints.fixedInlineSize - edges.all.inline; const availableBlockSize = (constraints.fixedInlineSize || Infinity) - edges.all.block; const childConstraints = { availableInlineSize, availableBlockSize }; const unconstrainedChildFragments = yield children.map((child) => { return child.layoutNextFragment(childConstraints); }); const unconstrainedSizes = []; const totalSize = unconstrainedChildFragments.reduce((sum, fragment, i) => { unconstrainedSizes[i] = fragment.inlineSize; return sum + fragment.inlineSize; }, 0); // 在子元素之间分配剩余空间。 const remainingSpace = Math.max(0, inlineSize - totalSize); const extraSpace = remainingSpace / children.length; const childFragments = yield children.map((child, i) => { return child.layoutNextFragment({ fixedInlineSize: unconstrainedSizes[i] + extraSpace, availableBlockSize }); }); // 位置片段。 let inlineOffset = 0; let maxChildBlockSize = 0; for (let fragment of childFragments) { fragment.inlineOffset = inlineOffset; fragment.blockOffset = edges.all.blockStart; inlineOffset += fragment.inlineSize; maxChildBlockSize = Math.max(maxChildBlockSize, fragment.blockSize); } return { autoBlockSize: maxChildBlockSize + edges.all.block, childFragments, }; } });
LayoutConstraints
对象具有percentageInlineSize
和percentageBlockSize
属性。这些属性表示在执行布局时百分比应解析的尺寸。
LayoutConstraints
对象具有blockFragmentationType
属性。当前布局应生成一个在blockFragmentationOffset
处进行分片的LayoutFragment
,如果可能的话。
当前布局可以根据blockFragmentationType
选择不对某个LayoutChild
进行分片,例如,如果该子元素具有类似于break-inside: avoid-page;的属性。
-
如果sizingMode为
"block-like"
,则:-
将fixedInlineSize设置为计算box的边框盒内联尺寸的结果(相对于box的书写模式),就像块容器一样。
-
如果box的块尺寸是auto,则将fixedBlockSize设置为null,否则将其设置为计算box的边框盒块尺寸的结果,就像块容器一样。
-
返回一个新的
LayoutConstraints
对象,包含:-
设置
fixedInlineSize
和availableInlineSize
为fixedInlineSize。 -
将
percentageInlineSize
设置为internalLayoutConstraints的内联轴百分比解析尺寸(相对于box的书写模式)。 -
将
fixedBlockSize
设置为fixedBlockSize。 -
如果fixedBlockSize不为null,则将
availableBlockSize
设置为fixedBlockSize,否则设置为internalLayoutConstraints在块轴上的可用空间(相对于box的书写模式)。 -
将
percentageBlockSize
设置为internalLayoutConstraints在块轴上的百分比解析尺寸(相对于box的书写模式)。
-
-
-
如果sizingMode为
"manual"
,则:-
返回一个新的
LayoutConstraints
对象,包含:-
将
fixedInlineSize
/fixedBlockSize
设置为internalLayoutConstraints的固定内联/块尺寸(相对于box的书写模式),由父布局强加。两者中的任意一个都可以为null。注意:请参阅§4.1 尺寸以了解可能发生此情况的不同场景。
-
将
availableInlineSize
/availableBlockSize
设置为internalLayoutConstraints的可用空间。 -
将
percentageInlineSize
/percentageBlockSize
设置为internalLayoutConstraints的百分比解析尺寸。
-
-
3.5. 分割与碎片化
[Exposed=LayoutWorklet] interfaceChildBreakToken
{ readonly attribute BreakTypebreakType
; readonly attribute LayoutChildchild
; }; [Exposed=LayoutWorklet] interfaceBreakToken
{ readonly attribute sequence<ChildBreakToken>childBreakTokens
; readonly attribute anydata
; }; dictionaryBreakTokenOptions
{ sequence<ChildBreakToken>childBreakTokens
; anydata
= null; }; enumBreakType
{"none"
,"line"
,"column"
,"page"
,"region"
};
LayoutChild
可以生成多个LayoutFragment
。
如果blockFragmentationType
不为none,则LayoutChild
可以在块方向上分片。此外,表示内联级内容的LayoutChild
可能会逐行分片,如果布局选项的childDisplay
(由layoutOptions
设置)为"normal"
。
可以使用前一个LayoutFragment
的breakToken
生成后续的LayoutFragment
。
这告诉子布局从编码在ChildBreakToken
中的点开始生成LayoutFragment
。
此示例还演示了如何使用breakToken
生成LayoutChild
的下一个片段。
它还演示了如何使用BreakToken
来符合LayoutConstraints
的blockFragmentationType
,它从上一个BreakToken
继续布局。
它返回一个带有FragmentResultOptions
的breakToken
,用于恢复布局。
registerLayout('indent-lines', class { static layoutOptions = {childDisplay: 'normal'}; static inputProperties = ['--indent', '--indent-lines']; *layout(children, edges, constraints, styleMap, breakToken) { // 确定我们的(内部)可用尺寸。 const availableInlineSize = constraints.fixedInlineSize - edges.all.inline; const availableBlockSize = (constraints.fixedBlockSize || Infinity) - edges.all.block; // 确定要缩进的行数和缩进量。 const indent = resolveLength(constraints, styleMap.get('--indent')); let lines = styleMap.get('--indent-lines').value; const childFragments = []; let childBreakToken = null; if (breakToken) { childBreakToken = breakToken.childBreakTokens[0]; // 删除所有已为其生成片段的子元素。 children.splice(0, children.indexOf(childBreakToken.child)); } let blockOffset = edges.all.blockStart; let child = children.shift(); while (child) { const shouldIndent = lines-- > 0; // 调整缩进的内联尺寸。 const childAvailableInlineSize = shouldIndent ? availableInlineSize - indent : availableInlineSize; const childConstraints = { availableInlineSize: childAvailableInlineSize, availableBlockSize, percentageInlineSize: availableInlineSize, blockFragmentationType: constraints.blockFragmentationType, }; const fragment = yield child.layoutNextFragment(childConstraints, childBreakToken); childFragments.push(fragment); // 定位片段。 fragment.inlineOffset = shouldIndent ? edges.all.inlineStart + indent : edges.all.inlineStart; fragment.blockOffset = blockOffset; blockOffset += fragment.blockSize; // 检查我们是否已超出块分割限制。 if (constraints.blockFragmentationType != 'none' && blockOffset > constraints.blockSize) { break; } if (fragment.breakToken) { childBreakToken = fragment.breakToken; } else { // 如果片段没有断点标记,我们将继续处理下一个子元素。 // 下一个子元素。 child = children.shift(); childBreakToken = null; } } const autoBlockSize = blockOffset + edges.all.blockEnd; // 返回我们的片段。 const result = { autoBlockSize, childFragments: childFragments, } if (childBreakToken) { result.breakToken = { childBreakTokens: [childBreakToken], }; } return result; } });
3.6. 边缘
[Exposed=LayoutWorklet] interfaceLayoutEdgeSizes
{ readonly attribute doubleinlineStart
; readonly attribute doubleinlineEnd
; readonly attribute doubleblockStart
; readonly attribute doubleblockEnd
; // Convenience attributes for the sum in one direction. readonly attribute doubleinline
; readonly attribute doubleblock
; }; [Exposed=LayoutWorklet] interfaceLayoutEdges
{ readonly attribute LayoutEdgeSizesborder
; readonly attribute LayoutEdgeSizesscrollbar
; readonly attribute LayoutEdgeSizespadding
; readonly attribute LayoutEdgeSizesall
; };
一个 LayoutEdges
对象被传入布局方法。它表示正在布局的当前框的 盒模型边缘 的大小。
这个 LayoutEdges
对象有 border
、
scrollbar
和 padding
属性。每个属性都表示其相应边缘的宽度。
这个 LayoutEdges
对象有 all
属性。它是一个便捷属性,表示 border
、scrollbar
和 padding
边缘的总和。
这个 LayoutEdgeSizes
对象表示每个 抽象方向(inlineStart
、inlineEnd
、blockStart
、blockEnd
)中边缘的宽度,以CSS像素为单位。
这个 inline
和 block
是 LayoutEdgeSizes
对象上的便捷属性,表示该方向的总和。
LayoutEdges
可能包含的内容。
<style> .container { width: 50px; height: 50px; } .box { display: layout(box-edges); padding: 10%; border: solid 2px; overflow-y: scroll; } </style> <div class="container"> <div class="box"></div> </div>
registerLayout('box-edges', class { *layout(children, edges, constraints, styleMap, breakToken) { edges.padding.inlineStart; // 5 (as 10% * 50px = 5px). edges.border.blockEnd; // 2 edges.scrollbar.inlineEnd; // UA-dependent, may be 0 or >0 (e.g. 16). edges.all.block; // 14 (2 + 5 + 5 + 2). } }
4. 与其他模块的交互
本节描述了其他CSS模块如何与CSS布局API进行交互。
4.1. 大小调整
用户代理必须使用 LayoutConstraints
对象与 当前布局 进行通信,以指示他们希望片段的大小。
如果用户代理希望强制对框设置大小,它可以使用 fixedInlineSize
和 fixedBlockSize
属性来实现。
根据 布局选项 的 sizing
(由类中的
layoutOptions
设置)的值,布局API容器可以通过不同方式接收大小信息。
如果 布局选项 的 sizing
值为 "block-like"
,那么传递给 布局API容器 的 LayoutConstraints
:
-
必须 基于 [css-sizing-3] 中的规则以及它所参与的格式化上下文,计算并设置
fixedInlineSize
,例如: -
必须 基于 [css-sizing-3] 中的规则以及它所参与的格式化上下文,计算并设置
fixedBlockSize
。如果 布局API容器 的 auto block size 无法提前确定,必须将fixedBlockSize
设置为null
。
如果 布局选项 的 sizing
值为 "manual"
,则用户代理在布局API容器的大小由所参与的格式化上下文强制设定时,除非提前强制设置特定的大小,否则不应预先计算 fixedInlineSize
和/或 fixedBlockSize
,例如:
-
如果 布局API容器 在 块格式化上下文 中,处于流内,并且具有 auto 行内尺寸,用户代理 必须 将
fixedInlineSize
设置为 拉伸适应的行内尺寸。
<style> #container { width: 100px; height: 100px; box-sizing: border-box; padding: 5px; } #layout-api { display: layout(foo); margin: 0 20px; } </style> <div id="container"> <div id="layout-api"></div> </div>
4.1.1. 定位布局大小调整
如果 布局API容器
是脱离文档流定位的,用户代理 必须 解算定位尺寸方程(CSS定位布局3 §8.1
绝对或固定定位、非替换元素的宽度,CSS定位布局3
§8.3 绝对或固定定位、非替换元素的高度),并设置合适的 fixedInlineSize
和 fixedBlockSize
。
<style> #container { position: relative; width: 100px; height: 100px; } #layout-api { display: layout(foo); top: 10px; bottom: 10px; left: 10px; right: 10px; position: absolute; } </style> <div id="container"> <div id="layout-api"></div> </div>
4.2. 定位
本级别规范中的所有定位由用户代理处理。
因此:
-
脱离文档流的子元素不会作为
LayoutChild
子元素出现。 -
inlineOffset
和blockOffset
代表了在任何定位和变换发生之前的片段位置。
-
“child-relative” 将是唯一传递给作者布局的子元素。如果它的位置在 (
inlineOffset
= 20
,blockOffset
= 30
), 那么它的最终位置将是 (25
,40
),相对定位由用户代理处理。 -
“child-absolute” 不会作为
LayoutChild
出现,而是由用户代理布局并定位。 -
上述示例也类似适用于黏性和固定定位的子元素。
<style> #container { display: layout(foo); position: relative; /* container is a containing block */ width: 100px; height: 100px; } #child-relative { position: relative; left: 5px; top: 10px; } </style> <div id="container"> <div id="child-relative"></div> <div id="child-absolute"></div> </div>
4.3. 溢出
可滚动溢出对于 布局API容器 由用户代理在本规范级别处理。
布局API容器应像块容器那样计算其可滚动溢出。
即使作者的 布局API容器 将片段放置在 可滚动溢出区域内,相对定位或变换可能会导致片段移动,使得它的 可滚动溢出区域不会产生溢出。
4.4. 分片
父布局可以通过设置 blockFragmentationType
和 blockFragmentationOffset
来要求 当前布局进行分片。
例如,[css-multicol-1] 布局会将 blockFragmentationType
设置为 "column"
并将 blockFragmentationOffset
设置为需要子片段的地方。
4.5. 对齐
布局API容器
的第一/最后基线集与块容器生成的方式相同(参见 CSS 盒对齐 3 §9.1
确定盒子的基线)。不过,流内子元素的顺序应由布局方法返回的顺序决定(通过 childFragments
)而不是文档顺序。
从 LayoutChild
中查询基线信息。
const fragment = yield child.layoutNextFragment({ fixedInlineSize: availableInlineSize, baselineRequests: ['alphabetic', 'middle'], }); fragment.baselines.get('alphabetic') === 25 /* 或其他值 */;
为 父布局 生成基线信息:
registerLayout('baseline-producing', class { *layout(children, edges, constraints, styleMap) { const result = {baselines: {}}; for (let baselineRequest of constraints.baselineRequests) { // baselineRequest === 'alphabetic' 或其他值。 result.baselines[baselineRequest] = 25; } return result; } });
5. 布局
本节描述了CSS布局API如何与用户代理的布局引擎交互。
5.1. 概念
布局定义是一个结构体,描述了 LayoutWorkletGlobalScope
需要的关于作者定义布局的信息(可由layout()函数引用)。它包括:
-
类构造函数,这是类的构造函数。
-
布局生成器函数,这是布局的生成器函数回调。
-
固有尺寸生成器函数,这是固有尺寸的生成器函数回调。
-
构造函数有效标志。
-
输入属性,这是一个列表,包含
DOMStrings
。 -
子输入属性,这是一个列表,包含
DOMStrings
。 -
布局选项,一个
LayoutOptions
。
文档布局定义是一个结构体,描述了文档需要的关于作者定义布局的信息(可由layout()函数引用)。它包括:
-
输入属性,这是一个列表,包含
DOMStrings
。 -
子输入属性,这是一个列表,包含
DOMStrings
。 -
布局选项,一个
LayoutOptions
。
5.2. 布局失效
每个盒子都有一个关联的布局有效标志。它可以是布局有效或布局无效。初始状态为布局无效。
每个盒子都有一个关联的固有尺寸有效标志。它可以是固有尺寸有效或固有尺寸无效。初始状态为固有尺寸无效。
-
将name设为layoutFunction的第一个参数。
-
将documentDefinition设为获取文档布局定义的结果,给定name。
如果获取文档布局定义失败,或documentDefinition为
"无效"
,则终止这些步骤。 -
将inputProperties设为documentDefinition的输入属性。
-
将childInputProperties设为documentDefinition的子输入属性。
-
对于inputProperties中的每个property,如果property的计算值已更改,将该布局有效标志设为布局无效,并将固有尺寸有效标志设为固有尺寸无效。
-
对于childInputProperties中的每个property,如果property的计算值已更改,将该布局有效标志设为布局无效,并将固有尺寸有效标志设为固有尺寸无效。
当用户代理重新计算盒子的计算样式时,或该盒子的子项计算样式重新计算时,使布局函数失效 必须被运行。
当由LayoutChild
表示的子盒子被添加或移除到盒子树中,或者其布局因计算样式更改或子项更改而失效时,并且这种失效需要向上传播到盒子树,将当前布局有效标志设为布局无效,并将当前固有尺寸有效标志设为固有尺寸无效。
当布局API容器的计算样式更改,并且此更改影响了LayoutEdges
对象内的值时,将盒子的布局有效标志设为布局无效,并将盒子的固有尺寸有效标志设为固有尺寸无效。
如果计算样式的更改影响了LayoutConstraints
对象内的值,则只需将盒子的固有尺寸有效标志设为固有尺寸无效。
注意:由于LayoutConstraints
对象仅在布局函数中传递,因此无需使固有尺寸失效。
LayoutEdges
对象:
以下属性的更改可能会影响LayoutConstraints
对象:
注意:这仅描述了与CSS布局API相关的布局失效。所有盒子从概念上都有一个布局有效标志,这些更改通过盒子树传播。
5.3. 布局 Worklet
layoutWorklet
属性允许访问负责与布局相关的所有类的Worklet
。
layoutWorklet
的worklet 全局作用域类型为LayoutWorkletGlobalScope
。
partial interface CSS {
[SameObject] readonly attribute Worklet layoutWorklet
;
};
LayoutWorkletGlobalScope
是layoutWorklet
的全局执行上下文。
[Global=(Worklet,LayoutWorklet),Exposed=LayoutWorklet] interfaceLayoutWorkletGlobalScope
: WorkletGlobalScope { void registerLayout(DOMStringname
, VoidFunctionlayoutCtor
); };
5.4. 注册布局
[Exposed=LayoutWorklet] dictionaryLayoutOptions
{ ChildDisplayTypechildDisplay
= "block"; LayoutSizingModesizing
= "block-like"; }; [Exposed=LayoutWorklet] enumChildDisplayType
{"block"
,"normal"
, }; [Exposed=LayoutWorklet] enumLayoutSizingMode
{"block-like"
,"manual"
, };
文档有一个 映射,存储文档布局定义。初始时该映射为空;当调用 registerLayout(name, layoutCtor)
时填充。
LayoutWorkletGlobalScope
有一个 映射,存储布局定义。初始时该映射为空,当调用 registerLayout(name, layoutCtor)
时填充。
表示 布局 API 容器 的每个 盒子都有一个 映射,存储布局类实例。初始时该映射为空,当用户代理调用 确定内在尺寸 或 生成片段 时填充。
registerLayout(name, layoutCtor)
方法时,用户代理 必须 执行以下步骤:
-
令 layoutDefinitionMap 为
LayoutWorkletGlobalScope
的 布局定义 映射。 -
如果 layoutDefinitionMap[name] 存在,抛出 一个 "InvalidModificationError" DOMException 并终止所有这些步骤。
-
令 inputProperties 为一个空的
sequence<DOMString>
。 -
令 inputPropertiesIterable 为 Get(layoutCtor, "inputProperties") 的结果。
-
如果 inputPropertiesIterable 不为 undefined,则将 inputProperties 设置为 转换 inputPropertiesIterable 为
sequence<DOMString>
的结果。如果抛出异常,则重新抛出该异常并终止所有这些步骤。注意: 由输入属性获取器提供的 CSS 属性列表可以是自定义的或原生的 CSS 属性。
注意: CSS 属性列表可能包含简写属性。
注意: 为了使布局类向前兼容,CSS 属性列表还可以包含当前对用户代理无效的属性。例如
margin-bikeshed-property
。 -
令 childInputProperties 为一个空的
sequence<DOMString>
。 -
令 childInputPropertiesIterable 为 Get(layoutCtor, "childInputProperties") 的结果。
-
如果 childInputPropertiesIterable 不为 undefined,则将 childInputProperties 设置为 转换 childInputPropertiesIterable 为
sequence<DOMString>
的结果。如果抛出异常,则重新抛出该异常并终止所有这些步骤。 -
令 layoutOptionsValue 为 Get(layoutCtor, "layoutOptions") 的结果。
-
令 layoutOptions 为 转换 layoutOptionsValue 为
LayoutOptions
的结果。如果抛出异常,则重新抛出该异常并终止所有这些步骤。 -
如果 IsConstructor(layoutCtor) 的结果为 false,抛出 一个 TypeError 并终止所有这些步骤。
-
令 prototype 为 Get(layoutCtor, "prototype") 的结果。
-
令 intrinsicSizes 为 Get(prototype,
"intrinsicSizes"
) 的结果。 -
如果 IsCallable(intrinsicSizes) 的结果为 false,抛出 一个 TypeError 并终止所有这些步骤。
-
如果 intrinsicSizes 的
[[FunctionKind]]
内部槽不是"generator"
,抛出 一个 TypeError 并终止所有这些步骤。 -
令 layout 为 Get(prototype,
"layout"
) 的结果。 -
如果 IsCallable(layout) 的结果为 false,抛出 一个 TypeError 并终止所有这些步骤。
-
如果 layout 的
[[FunctionKind]]
内部槽不是"generator"
,抛出 一个 TypeError 并终止所有这些步骤。 -
令 definition 为一个新的 布局定义,其属性为:
-
设置 layoutDefinitionMap[name] 为 definition。
-
排队任务 以运行以下步骤:
-
令 documentDefinition 为一个新的 文档布局定义,其属性为:
-
如果 documentLayoutDefinitionMap[name] 存在,执行以下步骤:
-
令 existingDocumentDefinition 为 获取 documentLayoutDefinitionMap[name] 的结果。
-
如果 existingDocumentDefinition 是
"invalid"
,终止所有这些步骤。 -
如果 existingDocumentDefinition 和 documentDefinition 不相等(即 输入属性、子输入属性 和 布局选项 不同),则:
设置 documentLayoutDefinitionMap[name] 为
"invalid"
。向调试控制台记录错误,指出相同的类已使用不同的
inputProperties
、childInputProperties
或layoutOptions
注册。
-
-
否则,设置 documentLayoutDefinitionMap[name] 为 documentDefinition。
class MyLayout { static get inputProperties() { return ['--foo']; } static get childrenInputProperties() { return ['--bar']; } static get layoutOptions() { return {childDisplay: 'normal', sizing: 'block-like'} } *intrinsicSizes(children, edges, styleMap) { // Intrinsic sizes code goes here. } *layout(children, edges, constraints, styleMap, breakToken) { // Layout code goes here. } }
5.5. 布局引擎
Promises
-
更好的错误报告。
-
可能更符合开发者的习惯。
生成器
-
更“严格” - 只能执行布局操作。不必限制每次调用中哪些 Promise API 可用。
-
可能具有更好的绑定开销。
5.5.1. 请求对象
[Exposed=LayoutWorklet] interfaceIntrinsicSizesRequest
{ }; [Exposed=LayoutWorklet] interfaceLayoutFragmentRequest
{ }; typedef (IntrinsicSizesRequest or LayoutFragmentRequest)LayoutFragmentRequestOrIntrinsicSizesRequest
;
IntrinsicSizesRequest
具有内部槽:
-
[[layoutChild]]
是一个LayoutChild
, 这是必须计算固有尺寸的子元素。
LayoutFragmentRequest
具有内部槽:
-
[[layoutChild]]
是一个LayoutChild
, 这是必须为其生成片段的子元素。 -
[[layoutConstraints]]
是一个LayoutConstraintsOptions
字典,这是LayoutChild
布局算法的输入约束。 -
[[breakToken]]
是一个ChildBreakToken
对象, 这是布局必须恢复的中断标记。
作者提供的布局类上的 layout 方法和固有尺寸方法是生成器函数,而不是常规的 JavaScript 函数。这样做是为了让用户代理支持异步和并行布局引擎。
当作者在 layoutNextFragment()
方法上调用 LayoutChild
时,
用户代理不会同步生成一个 LayoutFragment
来返回给作者的代码。
相反,它会返回一个 LayoutFragmentRequest
。
对作者而言,这个对象是完全不透明的,但它包含内部槽,封装了 layoutNextFragment()
方法调用。
当从布局生成器对象中生成一个或多个 LayoutFragmentRequest
时,
用户代理的布局引擎可能与其他工作异步运行该算法,和/或在不同的执行线程上运行。当引擎生成一个或多个 LayoutFragment
时,
用户代理将用生成的 LayoutFragment
来“触发”生成器对象。
同样适用于 intrinsicSizes()
方法。
class LayoutEngine { // This function takes the root of the box-tree, a LayoutConstraints object, and a // BreakToken to (if paginating for printing for example) and generates a // LayoutFragment. layoutEntry(rootBox, rootPageConstraints, breakToken) { return layoutFragment({ box: rootBox, layoutConstraints: rootPageConstraints, breakToken: breakToken, }); } // 该函数接收一个 LayoutFragmentRequest 并调用相应的函数 // layout algorithm to generate the a LayoutFragment. layoutFragment(fragmentRequest) { const box = fragmentRequest.layoutChild; const algorithm = selectLayoutAlgorithmForBox(box); const fragmentRequestGenerator = algorithm.layout( fragmentRequest.layoutConstraints, box.children, box.styleMap, fragmentRequest.breakToken); let nextFragmentRequest = fragmentRequestGenerator.next(); while (!nextFragmentRequest.done) { // 用户代理可能会决定执行布局以生成片段 // parallel on separate threads. This example performs them synchronously // in order. let fragments = nextFragmentRequest.value.map(layoutFragment); // 用户代理可能会在恢复此布局工作之前中断去执行其他工作(例如垃圾回收) // 此示例只是同步执行布局,无法进行中断。 nextFragmentRequest = fragmentRequestGenerator.next(fragments); } return nextFragmentRequest.value; // Return the final LayoutFragment. } }
5.6. 执行布局
// This is the final return value from the author defined layout() method. dictionaryFragmentResultOptions
{ doubleinlineSize
= 0; doubleblockSize
= 0; doubleautoBlockSize
= 0; sequence<LayoutFragment>childFragments
= []; anydata
= null; BreakTokenOptionsbreakToken
= null; }; dictionaryIntrinsicSizesResultOptions
{ doublemaxContentSize
; doubleminContentSize
; };
5.6.1. 确定固有尺寸
确定固有尺寸算法定义了用户代理如何查询作者定义的布局,获取盒子的固有尺寸信息。
注意: 确定固有尺寸算法允许用户代理缓存任意数量的先前调用以便重用。
-
令layoutFunction为box的布局()。
-
如果layoutFunction的固有尺寸有效标志为固有尺寸有效,则用户代理可以使用先前调用中的固有尺寸。如果是这样,它可以中止所有这些步骤并使用先前的固有尺寸值。
-
令name为layoutFunction的第一个参数。
-
令documentDefinition为获取文档布局定义给定的name的结果。
如果获取文档布局定义返回失败,或documentDefinition为
"invalid"
,则让box回退到流布局,并中止所有这些步骤。 -
令workletGlobalScope为来自布局Worklet中工作域的WorkletGlobalScopes列表中的
LayoutWorkletGlobalScope
。用户代理必须拥有并从至少两个
LayoutWorkletGlobalScope
中选择,除非用户代理处于内存限制下。注意: 这是为了确保作者不依赖于能够在全局对象上存储状态或不可再生成的类状态。
用户代理也可以在此时创建一个 WorkletGlobalScope,给定布局
Worklet
。
-
令definition为获取布局定义给定的name和workletGlobalScope的结果。
-
令layoutInstance为获取布局类实例给定box、definition和workletGlobalScope的结果。
-
令inputProperties为definition的输入属性。
-
令children为一个新的列表。
-
对于每个childBox在childBoxes中执行以下子步骤:
-
令edges为一个新的
LayoutEdgeSizes
,其填充了box的所有计算值的盒模型边缘。 -
令styleMap为一个新的
StylePropertyMapReadOnly
,仅填充了box的inputProperties列表中的计算值。 -
在此阶段,如果children、styleMap等同于先前的调用,用户代理可以重用先前调用中的固有尺寸。如果是这样,则让固有尺寸为缓存的固有尺寸,并中止所有这些步骤。
-
令intrinsicSizesGeneratorFunction为definition的固有尺寸生成器函数。
-
令intrinsicSizesGenerator为调用(intrinsicSizesGeneratorFunction,layoutInstance,«children,edges,styleMap»)的结果。
-
令intrinsicSizesValue为运行生成器给定的intrinsicSizesGenerator和
"intrinsic-sizes"
的结果。 -
令intrinsicSizes为转换intrinsicSizesValue为
IntrinsicSizesResultOptions
的结果。 -
设置box的固有尺寸:
-
令intrinsicSizes的
minContentSize
为box的最小内容尺寸。 -
令intrinsicSizes的
maxContentSize
为box的最大内容尺寸。
-
5.6.2. 生成片段
生成片段算法定义了用户代理如何为作者定义的布局生成盒子的片段。
注意: 生成片段算法允许用户代理缓存任意数量的先前调用以便重用。
-
令layoutFunction为box的布局()。
-
如果layoutFunction的布局有效标志为布局有效,用户代理可以使用先前调用中的固有尺寸。如果是这样,它可以中止所有这些步骤并使用先前的固有尺寸值。
-
令name为layoutFunction的第一个参数。
-
令documentDefinition为获取文档布局定义给定的name的结果。
如果获取文档布局定义返回失败,或documentDefinition为
"invalid"
,则让box回退到流布局,并中止所有这些步骤。 -
令workletGlobalScope为来自布局
LayoutWorkletGlobalScope
的工作域列表中的一个。用户代理必须拥有并从至少两个
LayoutWorkletGlobalScope
中选择,除非用户代理处于内存限制下。注意: 这是为了确保作者不依赖于能够在全局对象上存储状态或无法再生成的类状态。
用户代理也可以在此时创建一个 WorkletGlobalScope,给定布局
Worklet
。 -
运行调用布局回调给定的name、box、childBoxes、internalLayoutConstraints、internalBreakToken和workletGlobalScope,可选地并行。
-
令definition为获取布局定义给定name和workletGlobalScope的结果。
-
令layoutInstance为获取布局类实例给定box、definition和workletGlobalScope的结果。
-
令inputProperties为definition的输入属性。
-
令children为一个新的列表。
-
对于每个childBox在childBoxes中执行以下子步骤:
-
令edges为一个新的
LayoutEdgeSizes
,其填充了box的所有计算值的盒模型边缘。 -
令layoutConstraints为创建布局约束对象给定的internalLayoutConstraints、box和sizingMode的结果。
-
令styleMap为一个新的
StylePropertyMapReadOnly
,仅填充了box的inputProperties列表中的计算值。 -
令breakToken为一个新的
BreakToken
,其中填充了internalBreakToken的相关信息。如果internalBreakToken为null,则令breakToken为null。
-
在此阶段,如果children、styleMap、layoutConstraints、breakToken等同于先前的调用,用户代理可以重用先前调用中的片段。如果是这样,则让输出片段为缓存的片段,并中止所有这些步骤。
-
令layoutGeneratorFunction为definition的布局生成器函数。
-
令layoutGenerator为调用(layoutGeneratorFunction,layoutInstance,«children,edges,layoutConstraints,styleMap,breakToken»)的结果。
-
令fragmentValue为运行生成器给定的layoutGenerator和
"layout"
的结果。 -
令fragment为转换fragmentValue为
FragmentResultOptions
的结果。 -
对于每个childFragment在fragment的
子片段
中执行以下步骤:-
如果childFragment的
[[generator]]
内部插槽不等于layoutGenerator,则让box回退到流布局,并中止所有这些步骤。
-
-
如果sizingMode为
"block-like"
: -
为box返回一个片段,其包含以下内容:
-
行内尺寸设置为inlineSize。
-
块大小设置为blockSize。
-
子片段设置为fragment的
子片段
列表。顺序很重要,因为这决定了它们的绘制顺序(如第2节 布局API容器所述)。它们相对于fragment的边框盒的位置应基于作者指定的inlineOffset
和blockOffset
。 -
片段化中断信息设置为fragment的
breakToken
。 -
令clonedData为调用StructuredSerializeForStorage获取fragment的
数据
的结果。用户代理必须将clonedData与片段一起存储。
-
5.6.3. 实用算法
-
令done为布尔值,初始化为
false
。 -
令nextValue为未定义。
-
执行以下子步骤,直到done为
true
:-
令nextFunction为获取的generator中的
"next"
的结果。 -
如果IsCallable(nextFunction)的结果为false,抛出TypeError并中止所有这些步骤。
-
令nextResult为调用Invoke(nextFunction,generator,«nextValue»)的结果。
如果抛出异常,返回失败并中止所有这些步骤。
-
令requestOrRequests为获取(nextResult中的
"value"
的结果)。 -
令done为获取的nextResult中的
"done"
的结果。 -
如果GetMethod(requestOrRequests,
@@iterable
)存在,则: -
令request为转换(requestOrRequests为
LayoutFragmentRequestOrIntrinsicSizesRequest
)的结果。如果抛出异常,重新抛出异常并中止所有这些步骤。
-
令result为生成生成器结果(request,generator,generatorType)的结果。
如果生成生成器结果返回失败,返回失败并中止所有这些步骤。
-
将nextValue设置为result。
-
继续。
用户代理可以乱序执行上面的循环,并且可以并行执行。但是requests和results的顺序必须保持一致。
注意:这允许用户代理在不同线程或异步运行适当的布局算法(例如与其他工作时间切片布局工作)。如果用户代理并行执行循环,则外部循环必须等待所有跨线程任务完成后才能再次调用生成器。它不能向作者返回部分结果。
-
-
返回调用获取的nextResult中的
"value"
的结果。
-
如果request是
IntrinsicSizesRequest
,则:-
令layoutChild为在request上查找内部槽
[[layoutChild]]
的结果。 -
令box为在layoutChild上查找内部槽
[[box]]
的结果。 -
如果box未附加到盒子树,返回失败并中止所有这些步骤。
注意:作者可能会保留来自先前调用的
LayoutChild
。假设一个盒子只能附加到盒子树一次,且不会被重复使用。 -
令internalIntrinsicSizes为用户代理计算的box的边框盒最小/最大内容贡献的结果。
-
返回创建固有尺寸对象(request和internalIntrinsicSizes)的结果。
-
-
如果request是
LayoutFragmentRequest
,并且generatorType为"layout"
,则:-
令layoutChild为在request上查找内部槽
[[layoutChild]]
的结果。 -
令box为在layoutChild上查找内部槽
[[box]]
的结果。 -
如果box未附加到盒子树,返回失败并中止所有这些步骤。
注意:作者可能会保留来自先前调用的
LayoutChild
。假设一个盒子只能附加到盒子树一次,且不会被重复使用。 -
令childLayoutConstraints为在request上查找内部槽
[[layoutConstraints]]
的结果。 -
令childBreakToken为在request上查找内部槽
[[breakToken]]
的结果。 -
令internalFragment为用户代理基于box、childLayoutConstraints和childBreakToken生成的片段的结果。
-
返回创建布局片段(generator、request和internalFragment)的结果。
-
-
返回失败(以上分支均未被采用)。
6. 安全性考虑
这些功能没有已知的安全问题。
7. 隐私考虑
这些功能没有已知的隐私问题。