CSS 视觉格式化模型(一):盒模型和盒类型
CSS 视觉格式化模型系列
前言
我们的浏览器说到底也就是一种信息的展示,无论是文字,图片还是其他媒体内容。内容的展示其实就是将内容按照一定的规则来对文档信息(HTML
文档和 CSS
)进行计算并渲染到屏幕上,这本质上上和现实中的印刷排版还是类似的,只不过载体不同。视觉格式化模型就是这套规则。我将分几篇文章来写一写我阅读标准的一些笔记。
视觉格式化模型
visual formatting model
对应CSS2.2的第九和第十章,我认为是CSS2.2
中最核心的部分。
盒模型 box model
盒模型是我们了解视觉格式化模型的基础,我们的浏览器就是将文档中的元素转化为一个一个的盒子再来进行排版和渲染的,盒是浏览器排版和渲染的基本单位。盒模型描述了一个为文档树中的元素生成的并根据视觉格式化模型进行布局的矩形框。
我们先来了解一个盒子的基本结构(见下图)。一个盒子必然有一个 content area
和可选的 margin
,padding
和 border
。margin
表示外边距,padding
表示内边距,border
表示边框。content area
的宽高由盒内的内容以及视觉格式化模型的规则来决定。外边距总是透明的,内边距和边框的背景样式由元素的 background
决定。
当 margin
和 padding
的值是百分比时,注意的是这个百分比是根据该元素的包含块 containing block
的 width
来计算的。每一个元素生成的盒就是其后代元素的盒的包含块,这里说的该元素的包含块是指该元素所在的包含块,而不是该元素生成的包含块。如果包含块的宽度是由该元素决定的(即包含块没有设置宽度,由该元素撑开的情况),这种情况下产生的布局是 CSS2.2
中未定义的(即由具体实现的浏览器决定)。
以包含块的宽度为基准的原因就是为了在水平和垂直放下上有相同的留白,无论是外边距还是内边距都是为了分隔盒,让排版更加的清晰,容易阅读。而水平和垂直方向的边距用相同的基准也是符合直觉的。而为什么以宽度而不是高度为基准其实也很简单,我们的阅读习惯是从左向右的。
margin
和 padding
区别主要有三个个,第一是 margin
可以取值 auto
而 padding
不可以,第二是 margin
值可以为负但是 padding
不可以,第三是垂直方向上的 margin
对非替换的行内元素是不会产生任何效果。
可替换元素指的是独立于
CSS
的外部对象,它们的内容不受当前文档的样式的影响。CSS 可以影响可替换元素的位置,但不会影响到可替换元素自身的内容。某些可替换元素,例如<iframe>
元素,可能具有自己的样式表,但它们不会继承父文档的样式。常见的可替换元素有iframe
,img
,ebmed
和video
等,有些元素只在特定情况下被当做可替换元素,比如<option>
,<audio>
,<canvas>
,<object>
,<applet>
。
margin
和 padding
都是简写属性,他们都有上下左右四个分量。简写属性接受多个值,用逗号分隔开。如果只给一个值,那么该值作用于四个分量;如果给两个值,那么第一个值作用于上下分量,而第二个值作用于左右分量;如果给三个值,那么第一个值作用于上分量,第二个值作用于左右分量,第三个值作用于下分量;如果给四个值,那么分别作用域上下左右分量。这种规则也适用于 border
。
border
是简写属性,他有上下左右四个分量 border-top
,border-right
,border-bottome
和 border-left
,不过和 margin/padding
不同,border
简写属性不能对四个方向设置不同的值。border
的四个分量依然是简写属性,他们还有 color
,style
和 width
三个分量。而 border-color
,border-style
和 border-width
也可以作为简写属性直接赋值,他们能够接受一到四个值,和 margin/padding
的规则一样,可以为四个方向应用不同的值。
border
相关的属性很多并且功能有所重叠,我们可以灵活运用,比如用 border
设置好四个方向的值在用 border-left
对 border
进行覆盖实现独立的效果等。
还有需要注意一点的是元素的 background
是覆盖到 border
的。
<div style="width: 100%; text-align:center;">
<div style="display: inline-block; height: 300px; width: 300px; background: pink; border: 3px dashed rgba(0, 0, 0, 0.3)"></div>
</div>
盒模型还有一点要注意的是 box-sizing
属性,该属性决定了如何计算元素的高度和宽度。默认设定下,宽度和高度只表示 content area
,也就是 content-box
。而如果设置 box-sizing
为 border-box
的话,宽度和高度将包括 padding
和 border
。
盒的类型
上面已经介绍了盒的基本结构,想要了解视觉格式化模型的具体内容我们还需要了解盒子的类型。
基本上我们的盒子可以分为两大类,一类是块级元素对应的盒子,一类是行内元素对应的盒子。但是在文档中将这两大类由进行了详细的说明,并引出了很多相似的概念,我们需要区分清楚。
- 包含块
containing block
:一个元素生成的盒子是其后代盒子的包含块,通常情况下根元素所在的包含块就是初始包含块,他的大小取决于视口viewport
(浏览器环境下就是浏览器窗口)。包含块在标准中的具体定义看下面的介绍。 - 盒
box
:一个抽象的概念,由CSS
引擎根据文档中的内容所创建,主要用于文档元素的定位、布局和格式化等用途。盒子与元素并不是一一对应的,有时多个元素会合并生成一个盒子(列表元素),有时一个元素会生成多个盒子(如匿名盒子)。 - 块级元素
block-level element
:源文档中那些被格式化成视觉上的块的元素(例如,段落),元素的display
为block
、list-item
、table
时,该元素将成为块级元素。元素是否是块级元素仅是元素本身的属性,并不直接用于格式化上下文的创建或布局。 - 块级盒
block-level box
:由块级元素生成,是参与块格式化上下文的盒。每个块级元素生成一个主块级盒(principal block-level box
),用来包含后代盒及生成的内容,并且任何定位方案都与该盒有关。有些块级元素可能会生成除主盒外的额外的盒,比如 `list-item’ 元素。这些额外的盒根据主盒来放置。 - 块容器盒
block container box
:除了表格盒与替换元素外,一个块级盒也是块容器盒。一个块容器盒要么只包含块级盒,要么建立了行内格式化上下文并因此只包含行内盒。不是所有的块容器盒都是块级盒:非替换的行内块(比如inline-block
)与非替换的表格单元是块级容器盒,但不是块级盒。 - 块盒
block box
:作为块级容器的块级盒也叫块盒。 - 块 block:块级盒(
block-level box
),块容器盒(block container box
)和 块盒(block box
)这三个术语在没有歧义的时候就简称为“块(block
)”。 - 行内级元素
inline-level element
:行内级元素是源文档中那些不会形成新内容块的元素,内容分布于多行(例如,强调段落中的一部分文本,行内图片等等)。display
为inline
、inline-block
、inline-table
的元素称为行内级元素。与块级元素一样,元素是否是行内级元素仅是元素本身的属性,并不直接用于格式化上下文的创建或布局。 - 行内级盒
inline-level box
:行内级元素生成行内级盒,即参与行内格式化上下文的盒。行内级盒子包括行内盒和原子行内级盒两种,区别在于该盒是否参与行内格式化上下文的创建。 - 行内盒
inline box
:特殊的行内级盒,其内容参与包含行内格式化上下文创建,display
值为inline
的非替换元素会生成一个行内盒。比如inline-block
虽然也是行内级盒,但是内部是块容器盒,对包含行内格式上下文来说它就像一个不透明的行内元素。与块盒类似,行内盒也分为具名行内盒和匿名行内盒(anonymous inline box
)两种。 - 原子行内级盒
atomic inline-level box
:不创建行内格式化上下文的行内级盒子,(例如,行内级替换元素,inline-block
元素和inline-table
元素),它们作为单一的不透明盒(opaque box
)参与包含行内格式化上下文。原子行内级盒子的内容不会拆分成多行显示。 - 行盒(也有翻译为行框的)
line box
:在行内格式化上下文中,盒是从包含块的顶部开始一个挨一个水平放置的。这些盒之间的水平外边距,边框和内边距都有效。盒可能会以不同的方式垂直对齐:以它们的底部或者顶部对齐,或者以它们里面的文本的基线对齐。包含来自同一行的盒的矩形区域叫做行盒。虽然标准中没有明确指出,但是行盒应该是块级的。行盒是我们无法看到也无法用语言接触到的一个概念上的盒。
一个盒子的类型不一定是唯一的,取决于你看盒子的角度,比如
inline-block
从父元素的盒角度来看,它是一个原子行内级盒,但是相对于内部的元素他也是一个块容器盒。
虽然概念看上去很多,但大部分都是不同的角度看同一个东西得出的不同概念,我们只需要对应起来理解。块级元素对应行内级元素,他们一般是由 display
来决定的;块级盒和行内级盒,他们由对应的块级元素或行内及元素生成,参与对应各格式上下文;块容器盒没有对应的行内级概念(行内格式上下文中的行框概念可以类比),他的角度是包含,即当前盒和它的后代之间的关系,它虽然不参与内部的子元素的布局和定位,但是他是内部子元素的布局定位的基准,可以简单理解为边界。
我们编写
HTML
和CSS
都是以元素为单位,但是浏览器的工作过程却是把元素转换成一个一个的盒子,也就是手我们编写文档是以元素为单位,浏览器渲染文档是以盒为单位。
在标准中,块级元素块级盒和行内元素行内盒是并列的,但我认为块级元素块级盒才是排版的基本单元。所谓的行内元素行内盒都是在某个块内部的。一个块级盒内部要么全部是块级盒;要么建立了行内格式上下文,只有行内级盒。但其实行内格式上下文都是在行盒内 line boxex
,而行框其实是块级的,也就是说从根元素 <html>
开始,块级盒里面只有块级盒,我们所见到的全是行内级元素的块容器盒,其实内部也是一个个垂直排列的行盒,只不过行盒我们无法看到。
CSS2.2
中没有明确说明行盒是块级的,我在winter
的文章,不过我认为这种逻辑是比较合理的,整个视觉格式化模型的结构在这种理解下变得非常有逻辑和清晰。
<!-- Some text 虽然没有被任何元素包裹,其实他显示包裹了一层匿名块盒,匿名块盒内部又包裹了一层匿名行盒 -->
<div >
<div style="margin: 0 auto;border: 1px solid;width: 200px;font-size: 20px;">
Some text
<p style="border: 2px solid red">More text</p>
</div>
</div>
More text
当一个行内盒包含流内块级元素(in-flow
,没有因浮动或定位而脱离文档流的块级元素)时,行内盒(以及属于同一个行框的行内祖先)会被破坏,分布到该块级元素(以及任何连续的或只被可合并的空白符和/或流外元素隔开的块级兄弟:多个块级元素的情况)周围。行内盒会被切成两个部分(这两个部分各有一边是空的,也就是只有三条边),分布于块级元素的两边。拆分前的行框和拆分后的行框都被包进匿名块盒,这些匿名块盒会作为分隔块级盒的兄弟。当这样一个行内盒受到相对定位的影响时,任何由此产生的位移也会影响行内盒里面的块级盒。
<!-- 该例中p为行内元素,其中包含了两个匿名的文本块和一个块级元素,
最终的结果就是最外层的div产生一个块盒,里面的两个文本块产生两个匿名块盒,作为span块级盒的兄弟。
并且我们可以看到被块级元素分隔开的两个文本块的边框是断开的,这部分细节我们在下一篇格式上下文里面讲 -->
<div>
<p style="display: inline; border: 1px solid red">
This is anonymous text before the SPAN.
<span style="display: block; border: 2px solid blue;">This is the content of SPAN.</span>
This is anonymous text after the SPAN.
</p>
</div>
This is anonymous text before the SPAN.This is the content of SPAN.This is anonymous text after the SPAN.
包含块 containing block
元素(生成的)盒的位置和大小有时是根据一个特定矩形计算的,叫做该元素的包含块(containing block
)。元素包含块的定义如下:
- 根元素所在的包含块是一个被称为初始包含块的矩形。对于连续媒体,尺寸取自视口的尺寸,并且被固定在画布开始的位置;对于分页媒体就是页区(
page area
)。初始包含块的’direction’属性与根元素的相同 - 对于其它元素,如果该元素的
position
是relative
或者static
,包含块由其最近的块容器祖先盒的内容边界形成 - 如果元素具有
position: fixed
,包含块由连续媒体的视口或者分页媒体的页区建立 - 如果元素具有
position: absolute
,包含块由最近的position
为absolute
,relative
或者fixed
的祖先建立,按照如下方式:- 如果该祖先是一个行内元素,包含块就是环绕着为该元素生成的第一个和最后一个行内盒的内边距框的边界框(
bounding box
)。在CSS 2.2
中,如果该行内元素被跨行分割了,那么包含块是未定义的 - 否则,包含块由该祖先的内边距边界形成
- 如果该祖先是一个行内元素,包含块就是环绕着为该元素生成的第一个和最后一个行内盒的内边距框的边界框(
display 属性
display
属性确定元素的类型和生成的盒的类型,以及内部的元素应用何种布局。display
属性会设置元素的内部和外部显示类型。外部类型确定了元素如何参与流 flow
的布局(关于 flow
的介绍在系列文章的第二篇),内部类型确定子元素的布局(这也就是我上面说的元素生成的盒不一定是唯一的,比如 inline-block
。有些 display
属性的布局定义有一份完全单独的标准,比如 flex
属性就有自己单独的标准 CSS Flexible Box Layout Module Level 1。
display
的各种取值的含义如下:
block
:这个取值会让元素生成一个主块盒。inline-block
:这个取值会让元素生成一个行内块级容器inline-level block container
。元素内部会被格式化为一个块盒;对于所在流,元素本身被格式化为一个原子行内级盒。inline
:这个取值会让元素生成一个或多个行内盒。list-item
:这个取值会让元素(比如li
元素)生成一个主块盒和一个标记盒(列表项前的标记)。none
:这个取值下元素不会出现在格式化结构formatting structure
中,对可视化媒体即不会生成盒也不会对布局有任何影响,对于最终的渲染结果,元素就好像不存在一样。元素的后代同样也不会生成盒,元素被完全移除格式化结构。后代元素的display
属性不会影响上述行为。取值为none
不会创建任何可见的盒;如果你想要元素在格式化结构中生成盒但是元素不可见,可以使用CSS
的另一个属性visibility
。table, inline-table, table-row-group, table-column, table-column-group, table-header-group, table-footer-group, table-row, table-cell, and table-caption
:这些取值会让元素表现的像一个表格元素。
除了定位 positioning
,浮动 float
和根元素,其他元素的 display
属性的计算值和指定值相同。关于例外的三者,我们会在下一篇文章进行讨论。
总结
本文主要是讲标准中的盒模型和盒的类型,盒是浏览器渲染文档的基本单元。我觉得这部分我们需要记住几点就行。
- 盒的结构。
- 文档被分成一个一个的块,根元素就是一个块盒。
- 一个块盒的子盒(直接子元素,嵌套的后代不在此列)要么全是块盒,要么全是行内级盒(其实行内级盒也是包含在一个看不见的行框中,而行框是块级的)。
- 块容器盒内的只要有一个块级元素,所有的行内元素都会被包裹上一层匿名块盒,为了保证 第
3
条中的内容。 - 块容器盒中未被任何元素包裹的文本将被一个匿名行内盒包裹(匿名行内盒外层就是匿名行块盒)