视觉格式化模型
在 CSS 中,视觉格式化模型(visual formatting model)描述了用户代理如何获取文档树、进行处理并将其显示在视觉媒体上。这包括了连续媒体(如计算机屏幕)和分页媒体(如书籍或通过浏览器打印功能打印的文档)。大部分信息对连续媒体和分页媒体同样适用。
在视觉格式化模型中,文档树中的每个元素都会根据盒模型生成零个或多个盒。这些盒的布局由以下因素决定:
- 盒的尺寸和类型。
- 定位方案(普通流、浮动和绝对定位)。
- 文档树中元素之间的关系。
- 外部信息(例如,视口大小、图像的固有尺寸等)。
关于视觉格式化模型的大部分信息都在 CSS2 中定义,然而,各种 CSS 布局模块对此信息进行了扩展。在阅读规范时,你会经常发现对 CSS2 中定义的模型的引用,因此,在阅读其他布局规范时,理解该模型及其在 CSS2 中使用的术语是很有价值的。
在本文中,我们将定义该模型并介绍一些相关的术语和概念,并链接到更具体的页面以获取更多详细信息。
视口的作用
在连续媒体中,视口(viewport)是浏览器窗口的可视区域。用户代理可以在视口大小改变时更改页面的布局——例如,当你调整窗口大小或改变移动设备的方向时。
如果视口小于文档的大小,那么用户代理需要提供一种滚动到文档未显示部分的方法。我们最常见的是在块级维度(block dimension)上滚动——在水平、从上到下的语言中是垂直滚动。然而,你也可能设计出需要在行内维度(inline dimension)上滚动的内容。
盒的生成
盒的生成是 CSS 视觉格式化模型的一部分,它根据文档的元素创建盒。生成的盒有不同的类型,这会影响它们的视觉格式化。生成的盒的类型取决于 CSS display
属性的值。
最初在 CSS2 中定义的 display
属性,在 CSS display、CSS 弹性盒布局、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
会生成多个盒(例如,一个主块盒和一个子标记盒)。而一些值(如 none
或 contents
)会导致元素和/或其后代根本不生成任何盒。
匿名盒
当没有 HTML 元素可用于生成盒时,就会创建一个匿名盒(anonymous box)。这种情况发生在,例如,当你在父元素上声明 display: flex
,而在其直接内部有一段未包含在其他元素中的文本。为了修复盒树,会围绕该文本段创建一个匿名盒。然后它会像一个伸缩项一样表现,但是,它不能像常规盒那样被选择和设置样式,因为没有元素可以定位。
<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>
body {
font: 1.2em sans-serif;
margin: 20px;
}
.flex {
display: flex;
}
.flex > * {
background-color: rebeccapurple;
color: white;
}
当文本段与块级元素穿插时,也会发生同样的事情。在下一个例子中,我在一个 <div>
中有一个字符串;在我的字符串中间是一个包含部分文本的 <p>
元素。
<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>
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>
后面的行盒被缩短以环绕浮动元素。盒的背景延伸到浮动元素的后面,因为浮动项已经脱离了文档流。
<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>
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),即 block
或 inline
。这个外部显示类型指的是盒在页面上与其他元素并存时的行为方式。
盒也有一个内部显示类型,决定了其子元素的行为方式。对于普通的块级和行内布局,或者说普通流,这个显示类型是 flow
。这意味着子元素也将是 block
或 inline
。
然而,内部显示类型也可能是像 grid
或 flex
这样的值,在这种情况下,直接子元素将显示为网格项或伸缩项。在这种情况下,该元素被描述为创建了一个网格或伸缩格式化上下文(formatting context)。在很多方面,这与块格式化上下文相似,但其子元素表现为伸缩项或网格项,而不是普通流中的项。
块级盒和行内级盒之间的交互在 display
属性参考中有详细描述。
此外,display 特定值的参考解释了这些格式化上下文在盒布局方面是如何工作的。
- CSS 网格布局模块
- CSS 弹性盒子布局模块
- CSS 多列布局模块
- CSS 表格模块
- CSS 列表和计数器模块
独立的格式化上下文
元素要么参与其包含块的格式化上下文,要么建立一个独立的格式化上下文。例如,一个网格容器为其子元素建立一个新的网格格式化上下文。
独立的格式化上下文会包含浮动元素,并且外边距不会跨越格式化上下文的边界折叠。因此,创建一个新的块格式化上下文可以确保浮动和外边距保留在盒内。要做到这一点,在你希望创建新块格式化上下文的盒上添加 display: flow-root
。
下面的例子展示了 display: flow-root
的效果。带有黑色背景的盒看起来包裹住了浮动项和文本。如果你移除 display: flow-root
,浮动项会从盒的底部伸出,因为它不再被包含。
<div class="container">
<div class="item">Floated</div>
<p>Text following the float.</p>
</div>
.container {
display: flow-root;
}
.item {
margin: 10px;
float: left;
}
块盒
在规范中,块盒(block boxes)、块级盒(block-level boxes)和块容器(block containers)在某些地方都被统称为块盒。这些东西有些许不同,只有在没有歧义的情况下才应使用术语“块盒”。
块容器
一个块容器(block container)要么只包含参与行内格式化上下文的行内级盒,要么只包含参与块格式化上下文的块级盒。因此,我们看到了上面解释的行为,即引入匿名盒以确保所有项都能参与块或行内格式化上下文。一个元素只有在它包含块级或行内级盒时才是一个块容器。
行内级盒和块级盒
这些是包含在块容器内,并分别参与行内或块级布局的盒。
块盒
一个块盒(block box)是一个既是块级盒又是块容器的盒。如 CSS display
中所述,一个盒可以是块级盒,但不是块容器(例如,它可能是一个伸缩或网格容器)。