视觉格式化模型

在 CSS 中,视觉格式化模型(visual formatting model)描述了用户代理如何获取文档树、进行处理并将其显示在视觉媒体上。这包括了连续媒体(如计算机屏幕)和分页媒体(如书籍或通过浏览器打印功能打印的文档)。大部分信息对连续媒体和分页媒体同样适用。

在视觉格式化模型中,文档树中的每个元素都会根据盒模型生成零个或多个盒。这些盒的布局由以下因素决定:

  • 盒的尺寸和类型。
  • 定位方案(普通流、浮动和绝对定位)。
  • 文档树中元素之间的关系。
  • 外部信息(例如,视口大小、图像的固有尺寸等)。

关于视觉格式化模型的大部分信息都在 CSS2 中定义,然而,各种 CSS 布局模块对此信息进行了扩展。在阅读规范时,你会经常发现对 CSS2 中定义的模型的引用,因此,在阅读其他布局规范时,理解该模型及其在 CSS2 中使用的术语是很有价值的。

在本文中,我们将定义该模型并介绍一些相关的术语和概念,并链接到更具体的页面以获取更多详细信息。

视口的作用

在连续媒体中,视口(viewport)是浏览器窗口的可视区域。用户代理可以在视口大小改变时更改页面的布局——例如,当你调整窗口大小或改变移动设备的方向时。

如果视口小于文档的大小,那么用户代理需要提供一种滚动到文档未显示部分的方法。我们最常见的是在块级维度(block dimension)上滚动——在水平、从上到下的语言中是垂直滚动。然而,你也可能设计出需要在行内维度(inline dimension)上滚动的内容。

盒的生成

盒的生成是 CSS 视觉格式化模型的一部分,它根据文档的元素创建盒。生成的盒有不同的类型,这会影响它们的视觉格式化。生成的盒的类型取决于 CSS display 属性的值。

最初在 CSS2 中定义的 display 属性,在 CSS displayCSS 弹性盒布局CSS 网格布局CSS ruby 布局模块中得到了扩展。此外,自 CSS2 以来,围绕 display 的一些术语也得到了更新和澄清。

CSS 会获取你的源文档并将其渲染到画布上。为此,它会生成一个中间结构,即盒树(box tree),它表示渲染文档的格式化结构。盒树中的每个盒代表其对应的元素(或伪元素)在画布上的空间和/或时间,而盒树中的每个文本运行同样代表其对应的文本节点的内容。

然后,对于每个元素,CSS 会根据该元素的 display 属性值生成零个或多个盒。

备注: 盒通常以其显示类型来称呼——例如,由一个 display: block 元素生成的盒被称为“块盒”(block box)或简称“块”(block)。但请注意,块盒(block boxes)、块级盒(block-level boxes)和块容器(block containers)都有细微的差别;更多详情请参阅下方的块盒部分。

主盒

当一个元素生成一个或多个盒时,其中一个是主盒(principal box),它在盒树中包含了其后代盒和生成的内容,并且也是参与任何定位方案的盒。

一些元素除了主盒外还可能生成额外的盒,例如 display: list-item 会生成多个盒(例如,一个主块盒和一个子标记盒)。而一些值(如 nonecontents)会导致元素和/或其后代根本不生成任何盒。

匿名盒

当没有 HTML 元素可用于生成盒时,就会创建一个匿名盒(anonymous box)。这种情况发生在,例如,当你在父元素上声明 display: flex,而在其直接内部有一段未包含在其他元素中的文本。为了修复盒树,会围绕该文本段创建一个匿名盒。然后它会像一个伸缩项一样表现,但是,它不能像常规盒那样被选择和设置样式,因为没有元素可以定位。

html
<div class="flex">
  I am wrapped in an anonymous box
  <p>I am in the paragraph</p>
  I am wrapped in an anonymous box.
</div>
css
body {
  font: 1.2em sans-serif;
  margin: 20px;
}

.flex {
  display: flex;
}

.flex > * {
  background-color: rebeccapurple;
  color: white;
}

当文本段与块级元素穿插时,也会发生同样的事情。在下一个例子中,我在一个 <div> 中有一个字符串;在我的字符串中间是一个包含部分文本的 <p> 元素。

html
<div class="example">
  I am wrapped in an anonymous box
  <p>I am in the paragraph</p>
  I am wrapped in an anonymous box.
</div>
css
body {
  font: 1.2em sans-serif;
  margin: 20px;
}

.example > * {
  background-color: rebeccapurple;
  color: white;
}

该字符串在盒树中被分割成三个盒。段落元素之前的字符串部分被包裹在一个匿名盒中,然后是 <p>,它生成一个盒,接着是另一个匿名盒。

关于这些匿名盒需要考虑的一点是,它们会从其直接父级继承样式,但你无法通过定位匿名盒来改变它们的外观。在我的例子中,我使用直接子选择器来定位容器的子元素。这不会改变匿名盒,因为它们本身不是“元素”。

当一个字符串被一个行内元素分割时,会创建行内匿名盒(inline anonymous boxes),例如,一个句子中包含一个用 <em></em> 包裹的部分。这会将句子分割成三个行内盒——在强调部分之前的一个匿名行内盒,被 <em> 元素包裹的部分,然后是最后一个匿名行内盒。与匿名块盒一样,这些匿名行内盒无法像 <em> 那样被独立设置样式;它们只是继承其容器的样式。

其他格式化上下文也会创建匿名盒。网格布局的行为与上面的 flexbox 示例相同,将文本字符串通过匿名盒转变为网格项。多列布局会在列周围创建匿名列盒;这些也无法被设置样式或以其他方式定位。表格布局会添加匿名盒来创建正确的表格结构——例如,如果没有 display: table-row 的盒,它会添加一个匿名表格行。

行盒

行盒(Line boxes)是包裹每行文本的盒。如果你浮动一个项目,然后紧跟一个带有背景色的块,你就可以看到行盒和其包含块之间的区别。

在下面的例子中,跟在浮动的 <div> 后面的行盒被缩短以环绕浮动元素。盒的背景延伸到浮动元素的后面,因为浮动项已经脱离了文档流。

html
<div class="float"></div>
<p class="following">
  This text is following the float, the line boxes are shortened to make room
  for the float but the box of the element still takes position in normal flow.
</p>
css
body {
  font: 1.2em sans-serif;
  margin: 20px;
}

.float {
  float: left;
  width: 150px;
  height: 150px;
  background-color: rebeccapurple;
  margin: 20px;
}

.following {
  background-color: #cccccc;
}

定位方案以及流内和流外元素

在 CSS 中,一个盒可以根据三种定位方案进行布局——普通流(normal flow)、浮动(floats)或绝对定位(absolute positioning)。

普通流

在 CSS 中,普通流包括块盒的块级格式化、行内盒的行级格式化,也包括块级和行级盒的相对定位和粘性定位。

阅读更多关于 CSS 中流式布局的内容。

浮动

在浮动模型中,一个盒首先根据普通流进行布局,然后脱离文档流并进行定位,通常是向左或向右。内容可以沿着浮动元素的一侧流动。

了解更多关于浮动的信息。

绝对定位

在绝对定位模型中(也包括 fixed 定位),一个盒完全从普通流中移除,并相对于一个包含块(对于固定定位,是视口)或 CSS 锚点定位中的一个或多个锚点元素来指定位置。

如果一个元素是浮动的、绝对定位的,或者是根元素,那么它被称为脱离文档流(out of flow)。如果一个元素不脱离文档流,那么它被称为在文档流内(in-flow)。

阅读关于 CSS 定位布局的内容。

格式化上下文和 display 属性

盒可以被描述为具有一个外部显示类型(outer display type),即 blockinline。这个外部显示类型指的是盒在页面上与其他元素并存时的行为方式。

盒也有一个内部显示类型,决定了其子元素的行为方式。对于普通的块级和行内布局,或者说普通流,这个显示类型是 flow。这意味着子元素也将是 blockinline

然而,内部显示类型也可能是像 gridflex 这样的值,在这种情况下,直接子元素将显示为网格项或伸缩项。在这种情况下,该元素被描述为创建了一个网格或伸缩格式化上下文(formatting context)。在很多方面,这与块格式化上下文相似,但其子元素表现为伸缩项或网格项,而不是普通流中的项。

块级盒和行内级盒之间的交互在 display 属性参考中有详细描述。

此外,display 特定值的参考解释了这些格式化上下文在盒布局方面是如何工作的。

独立的格式化上下文

元素要么参与其包含块的格式化上下文,要么建立一个独立的格式化上下文。例如,一个网格容器为其子元素建立一个新的网格格式化上下文

独立的格式化上下文会包含浮动元素,并且外边距不会跨越格式化上下文的边界折叠。因此,创建一个新的块格式化上下文可以确保浮动和外边距保留在盒内。要做到这一点,在你希望创建新块格式化上下文的盒上添加 display: flow-root

下面的例子展示了 display: flow-root 的效果。带有黑色背景的盒看起来包裹住了浮动项和文本。如果你移除 display: flow-root,浮动项会从盒的底部伸出,因为它不再被包含。

html
<div class="container">
  <div class="item">Floated</div>
  <p>Text following the float.</p>
</div>
css
.container {
  display: flow-root;
}

.item {
  margin: 10px;
  float: left;
}

块盒

在规范中,块盒(block boxes)、块级盒(block-level boxes)和块容器(block containers)在某些地方都被统称为块盒。这些东西有些许不同,只有在没有歧义的情况下才应使用术语“块盒”。

块容器

一个块容器(block container)要么只包含参与行内格式化上下文的行内级盒,要么只包含参与块格式化上下文的块级盒。因此,我们看到了上面解释的行为,即引入匿名盒以确保所有项都能参与块或行内格式化上下文。一个元素只有在它包含块级或行内级盒时才是一个块容器。

行内级盒和块级盒

这些是包含在块容器内,并分别参与行内或块级布局的盒。

块盒

一个块盒(block box)是一个既是块级盒又是块容器的盒。如 CSS display 中所述,一个盒可以是块级盒,但不是块容器(例如,它可能是一个伸缩或网格容器)。

另见