Web Platform Blog for China

Adobe

decor

Making the web awesome

归档

WebKit 中的 CSS 分片

分片是什么?

CSS 2.1 规范定义了一个框模型以表示文档的布局,几乎任何内容均可视为框。普通流节点(例如并非绝对定位的节点)从其父元素框顶部开始,一个子元素接着一个子元素地进行排列。如果某个元素的框太小,以致无法容纳所有内容,则称此内容为溢出,而此溢出或者可见,或者经过剪切。

分片与溢出不同,因为前者允许内容流经多个称为分片容器的框。到达当前分片容器的结尾时进行中断,然后布局继续出现在下一个分片容器中。创作者也可使用 CSS 强制在某个元素之后或之前进行中断,甚至可完全不中断。

需要记住一个重要的细节,即分片并非意味着从某个框溢出并在视觉上将该溢出移至另一个框。分片在布局期间进行,并且影响内容框的尺寸。

CSS 中在分片方面有一些规范:

  • CSS3 分页 – 文档经过适当的排列,使其可适合页面;页面充当分片容器。
  • CSS3 多列 – 元素定义若干填充其框的列以及排列内容之处;这些列充当分片容器。
  • CSS3 区域 – 所选内容形成一个在称为区域的框中排列的流;这些区域充当分片容器。

所有这些规范共同拥有一些常见的概念,这些概念在 CSS3 分片规范中有述。

流入两个区域的段落示例

流入两个区域的段落示例。

WebKit 中的分片

布局过程

要完全理解此处展示的概念,您应对 WebKit 中的布局方式有所了解。网上有一些不错的文章介绍这些基础知识,如 Bem 撰写的文章

简而言之,DOM 树在内部映射到一个呈现树,而后者用于构建每个节点的框属性。呈现树由 RenderBox、RenderBlock、RenderInline 等对象组成,这些对象体现了在 CSS2.1 中定义的概念。在布局期间,将遍历此树,并根据样式值计算呈现器的各种几何属性。最后,所有元素计算其框信息。

另一方面,您将注意到 WebKit 中通常使用“分页”或“页面”术语命名符号。这一点是一些历史原因所致,但大多数时候,分页概念均与分片概念(页面、列或区域)一一对应。

在 WebKit 中可能已有多种方式可实现分片行为。其中一种方式为每个元素片段配备一个呈现器。例如,有一个 10 行的简单段落,其中在第 4 行之后中断,那么第一个分片容器中要有一个 4 行的呈现器,而第二个分片容器中要有另一个 6 行的呈现器。这两个呈现器都将属于初始元素。此方法难以正确地实现和维护,因为它使基本代码变得复杂。此外,从安全角度看,此方法也有很大风险,因为它可能会引入许多细微的内存管理缺陷。

出于这些考虑,现已制订一项规则,即每个 DOM 节点最多只能有一个呈现器。通过移动整体框(不可分片的框,如行框 – 包围在每行文本四周的矩形)实现分片,以使这些框不因中断而重叠。在绘制阶段,通过将每个片段准确地放置在它应在分片容器中出现的位置,获得正确的呈现。下一节中详细介绍此主题。

在非强制中断的情况下,布局期间将对框进行位置调整(在基本代码中称为分页符)。此值表示在框不适合当前分片容器时默认布局位置与下一分片容器的移动偏移。对于每个框需要单独存储此偏移,不得将其存储为顶部位置的一部分,因为它不是呈现器的属性。这个布局技巧可帮助测量经过分片的框。例如,如果某个块与其容器(例如它是第一个子元素)一起折叠在顶部边距,并且该块也有分页符,则该偏移将传送到容器。这样做有道理,因为需要将块和容器两者放置在下一个分片容器中,即使后者将是大小不合适的子元素也是如此。

同样的规则也适用于块的第一个行框。此外,在接下来的布局期间,可按块流动的方向移动元素。需要重新计算行框分页符,因为在该元素改变位置之后,移动偏移很可能变得不同。

在当前的分片容器无法容纳某行时,将使用分页符移动该行

在当前的分片容器无法容纳某行时,将使用分页符移动该行

在当前的分片容器无法容纳某行时,将使用分页符移动该行。

举例来说,在上例的情况下,将在第一个分片容器中正常放置前四个行框。如果第一个分片容器只能容纳第五个行框的一部分,则需要为该行框定义一个分页符。此分页符将在逻辑上将该行移至下一个分片容器中。其余各行均正常放置在第二个分片容器中。

在强制中断的情况下,不再使用分页符,因为作者直接指定在何处进行中断。这些框放置在其容器内,因此其遵守中断条件。

每种类型的分片布局均有自己的专用行为。多列元素附加了 ColumnInfo 对象。其中包含有关列数和列宽、中断之间距离(如果这些列自动调整高度,则使此距离达到平衡)等的信息。还将此对象推至布局状态堆栈上,以便可从呈现树中的任意一点访问此对象。

区域实现当前是最复杂的利用分片的布局类型。内容节点呈现器不附加到其 DOM 父呈现器。而是将其移至一个特殊对象 RenderFlowThread,该对象在排列时设置分页上下文。这些呈现器需要保留并使用有关流线程的各种信息:区域大小、后代框如何在其所流入的每个区域中改变宽度。由于流线程的概念如此普遍,以致有计划将多列实现移植到此概念之上。

布局性能

从性能角度看,对于分片内容的布局过程总是慢一些,因为引擎无法应用对连续垂直内容做出的相同优化。例如,由于无法分片,因此更新元素的顶部边距可在垂直轴上移动该元素,但可能不需要重新布局。如果该元素四周是分片上下文,则移动该元素后,分片容器中可能无法再容纳它,从而触发中断。考虑到这种情况,总是需要对元素进行重新布局。

通常同时优化布局引擎的速度和内存用量。这意味着仅在必要时使用分片代码:如果有打印上下文、如果所排列的节点是多列元素的一部分或如果呈现器属于流线程。可在 LayoutState 类中找到用于实现分片代码的逻辑。在布局期间,将创建 LayoutState 对象的堆栈,并将其保存在呈现树的根 RenderView。任何呈现器均可查询此堆栈的顶部以确定能否将内容分片。

绘制阶段

WebKit 中呈现器的绘制由 RenderLayer 树处理。对于某些呈现器,引擎以正确顺序(例如对于 Z 索引限制)创建用于绘制文档的层。始终为进行分片的呈现器(如多列块或 RenderFlowThread 对象)创建层。因此,多列元素和区域创建堆叠上下文,并被绘制为单个项。

对于区域,当进行绘制操作时,引擎考虑呈现器的分片属性并移动层以修正位置,因此始终在正确的分片容器内执行绘制。不属于当前分片容器的内容将被剪切,因此引擎始终仅绘制应在某个区域中显示的内容。该机制在某种程度上类似于子画面(例如 CSS 子画面)的工作方式。

在两个区域中按对应于每种内容片段的偏移绘制 RenderFlowThread

在两个区域中按对应于每种内容片段的偏移绘制 RenderFlowThread

在两个区域中按对应于每种内容片段的偏移绘制 RenderFlowThread。

例如,比如说上面的段落流入区域 A 和 B。在绘制区域 A 时,调用流线程层在分片容器框中进行绘制。它将绘制前 4 行,然后绘制空白直至分片容器的底部,因为在布局时使用分页符移动了第 5 行。在绘制区域 B 时,将再次调用流线程层,在区域 B 片段开始(第 5 行的顶部)的分片容器框中按新的偏移进行绘制。随后将绘制该段落的最后 6 行。

多列块的情况类似。但是,主要的区别是多列块的分片容器没有自己的层。与多列块使用同一层时,内容可使用 3D 变换和视频标签等高级图形功能(有关加速层工作方式的详细信息,请参阅 WebKit 合成)。

在流线程的情况下,使其层与区域层配合工作是需要解决的一大难题。由于存在此问题,因此需要加速合成的内容层(例如视频层)将无法在流线程中正常工作。

总结

WebKit 中尚未完全实现新式分片概念。在处理强制中断的方面还有一些问题,并且仅为 break-inside 属性实现了 avoid 值。widows 和 orphans 属性最近才刚刚完全实现

在 CSS 区域方面,为了实现与层及合成子系统的平滑集成,还有一些工作要做。还需要进行大量测试以确保可良好地与其它 Web 功能集成。

本文在区域WebKit 中发表。

One Comment

  1. July 22, 2013 at 4:55 pm, Anonymous said: