框和布局
在 KB005: CSS 层叠 中已经介绍了 CSS 的重要之处。CSS 可以说是页面表现的基础, CSS 可以控制布局,控制元素的渲染。
布局是讲在电影画面构图中,对环境的布置、人物地位的调度、人物之间的关系、人物和景物的关系等的安排。大同小异,CSS 的布局也是一样。
CSS 的布局,包含两个大部分:
- 元素:元素及其组成,类似电影布局中人物的高矮胖瘦、衣着配饰等,以及场景中的桌椅板凳,物品等等。
- 定位:元素的位置, 类似电影场景的中对环境的设置,人物作为元素,他们之间的关系会影响其在场景中的位置。
总的说来,CSS 的布局比电影场景布局简单的多,因为 CSS 中要控制的元素都是死的,而人是活的有思想的(无思想的道具除外~~)。
元素和框模型
观察页面的构成元素,其中包括 SPAN DIV TABLE INPUT IMG 等等。
仔细观察每个元素,它们的形状都是矩形的,严格的说,元素形成了一个矩形的区域,这个区域就是 CSS 中最基本的布局要素, 常被称作 "box",你可以在很多 W3C CSS2.1 规范的原文中看到 "box" 这个字眼。本篇文章要讲的主要内容, 就是关于 "box" 组成及其变化的一系列规则,也就是今天要讲的内容:"Box Module",通常被译作“盒子模型”,有的地方被译作“框模型”。 因为 W3C 的很多地方用到了 "box" 这个词,有些地方译作“盒子”不大通顺,权且统统译作“框”。
本部分主要包括两部分:框的组成以及相关特性( property ),外边距折叠( Collapsing margins )。
框的构成以及相关 CSS 特性( property )
结构
为了给文档树中的各个元素排版定位(布局),浏览器会根据渲染模型1为每个元素生成四个嵌套的矩形框, 分别称作 content box、padding box、border box 和 margin box,它们是不可分割的,并可能会重合, 这就是 CSS 规范中描述的“框模型”(box model)。它是以 CSS 的角度去看一个元素被渲染后的抽象形态。 是一个元素自身的构成部分,不同于布局:多个元素在页面上的定位。
注:padding、border 和 margin 区域都包括 top、right、bottom、left
四部分。如图所示("LM" 代表 left margin,"RP" 代表 right padding,"TB" 代表 top
border……)。
上面的大框,代表一个元素生成的矩形区域( box ),每一个 box 都包括一个 content 区域(元素的内容,如文本,图形等)以及环绕其四周的 padding (元素的内边距,填充部分)、border (元素的边框) 和 margin (元素的外边距) 区域。
注:
- 元素在页面上定位及布局的规则,包括 'position' 和 'float' 等等,详见: W3C CSS2.1 -- 9 Visual formatting model
边界
上述四个区域(content、 padding、border 和 margin)分别有他们自己的边界,细化来说,每个区域都有 top、right、bottom、left 四个边界。
- Content 边界/内边界:Content 边界环绕在由该元素的宽和高决定的一个矩形上,这个尺寸通常由该元素渲染后的内容决定。 这四个 content 边界组成的矩形框就是该元素的 content box。
- Padding 边界:Padding 边界环绕在该元素的 padding 区域的四周,顾名思义,填充背景色,在此范围内有效。如果 padding 的宽度为0, 则 padding 边界与 content 边界重合。这四个 padding 边界组成的矩形框就是该元素的 Padding box。
- Border 边界:Border 边界环绕在该元素的 border 区域的四周,如果 border 的宽度为0,则 border 边界与 padding 边界重合。 这四个 border 边界组成的矩形框就是该元素的 border box。
- Margin 边界/外边界:Margin 边界环绕在该元素的 margin 区域的四周,如果 margin 的宽度为0,则 margin 边界与 border 边界重合。 这四个 margin 边界组成的矩形框就是该元素的 margin box。
一个简单的例子,来自 W3C 官方:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <HTML> <HEAD> <TITLE>Examples of margins, padding, and borders</TITLE> <STYLE type="text/css"> UL { background: yellow; margin: 12px 12px 12px 12px; padding: 3px 3px 3px 3px; /* No borders set */ } LI { color: white; /* text color is white */ background: blue; /* Content, padding will be blue */ margin: 12px 12px 12px 12px; padding: 12px 0px 12px 12px; /* Note 0px padding right */ list-style: none /* no glyphs before a list item */ /* No borders set */ } LI.withborder { border-style: dashed; border-width: medium; /* sets border width on all sides */ border-color: lime; } </STYLE> </HEAD> <BODY> <UL> <LI>First element of list</LI> <LI class="withborder">Second element of list is a bit longer to illustrate wrapping.</LI> </UL> </BODY> </HTML>
示意图:
相关 CSS 特性:padding、margin 和 border1
1). margin
'margin' 是 'margin-top', 'margin-right', 'margin-bottom', 'margin-left' 的简写,表明 margin 的大小范围。 它的值可以是 宽度值、百分比值或 'auto' 这3者之一,注意:宽度值必须带有单位。
margin 非 table 类型的元素,以及 table 类型中 table-caption, table 和 inline-table 这3类。例如 TD TR TH 等,margin 是不适用的。 并且,对于行内非替换元素(例如 SPAN),垂直方向的 margin 不起作用。
<div style="border:1px solid red;">
<span style="margin:100px; background:gray;">ddd</span>
</div>
可以看到,DIV 的上下 border 紧贴着灰色的 SPAN 元素。
另外,垂直方向上的不同元素的相邻的 margin 在某些情况下,会发生折叠的现象。
2). padding
padding 是 'padding-top', 'padding-right', 'padding-bottom', 'padding-left' 的缩写。默认值是 0。
可以应用到的元素:除 display 值是 ‘table-row-group’,‘table-header-group’, ‘table-footer-group’, ‘table-row’, ‘table-column-group’ 和 ’table-column’ 的所有元素。
3). border
border 是 'border- top', 'border-right', 'border-bottom', 'border-left' 的缩写。
border 包含3个部分,’border-width’,’border- color’,’border-style’,分别用来设置它的宽度,颜色和样式。适用于任何元素。
border-width 是 'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'。默认值是0。 可用值有‘thin’,‘medium’,‘thick’,以及常用的数值带单位的宽度值。
border-color 是 'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color' 的简写。默认值是 ‘color’ 特性的值。
border-style 是 'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style' 的简写。默认值是 none 特性的值。 可用值有:’none’,’hidden’,’dotted’,’dashed’,’solid’,’double’,’groove’,’ridge’,’inset’等。
注:
外边距折叠( Collapsing margins )1
在介绍 margin 特性的时候,简单的介绍了外边距折叠。
Collapsing margins,即外边距折叠,指的是毗邻的两个或多个外边距 (margin) 会合并成一个外边距。
其中所说的 margin 毗邻,可以归结为以下两点:
- 这两个或多个外边距没有被非空内容、padding、border 或 clear 分隔开。
- 这些 margin 都处于普通流中2。
下面详细的介绍其规则。
注:
- 详见:W3C CSS2.1 - 8.3.1 Collapsing margins
- 既 in-flow,非浮动元素,非定位元素(即规范内的非 absolutely positioned,这里不写为绝对定位元素是为了避免与样式设定中的绝对定位名词在中文中产生混淆)。
两个或多个毗邻的普通流中的块元素垂直方向上的 margin 会折叠
关键词解析
两个或多个
说明其数量必须是大于一个,又说明,折叠是元素与元素间相互的行为,不存在 A 和 B 折叠,B 没有和 A 折叠的现象。
毗邻
是指没有被非空内容、padding、border 或 clear 分隔开,说明其位置关系。
注意一点,在没有被分隔开的情况下,一个元素的 margin-top 会和它普通流中的第一个子元素(非浮动元素等)的 margin-top 相邻; 只有在一个元素的 height 是 "auto" 的情况下,它的 margin-bottom 才会和它普通流中的最后一个子元素(非浮动元素等)的 margin-bottom 相邻。
示例代码:
<div style="border:1px solid red; width:100px;"> <div style="margin:50px 0; height:50px; width:50px;"> <div style="margin:20px 0;"> <div style="margin:100px 0;">B</div> </div> </div> </div>
效果图:
以上代码中,margin 会把 B 的包含块撑开。
如果一个元素的 height 特性的值不是 auto,那么它的 margin-bottom 和它子元素的 margin-bottom 不算相邻,因此,不会发生折叠。 margin-top 没有此限制,所以是 100px,margin-bottom 没有折叠,只有 50px。
垂直方向
是指具体的方位,只有垂直方向的 margin 才会折叠,也就是说,水平方向的 margin 不会发生折叠的现象。
折叠后 margin 的计算
1). 参与折叠的 margin 都是正值
例子:
<div style="height:50px; margin-bottom:50px; width:50px; ">A</div>
<div style="height:50px; margin-top:100px; width:50px; ">B</div>
示意图:
在 margin 都是正数的情况下,取其中 margin 较大的值为最终 margin 值。
2). 参与折叠的 margin 都是负值
当 margin 都是负值的时候,取的是其中绝对值较大的,然后,从 0 位置,负向位移。
示例代码:
<div style="height:100px; margin-bottom:-75px; width:100px; ">A</div>
<div style="height:100px; margin-top:-50px; margin-left:50px; width:100px; ">B</div>
示意图:
3). 参与折叠的 margin 中有正值,有负值
如果,毗邻的 margin 中有正值,同时存在负值会怎样呢?有正有负,先取出负 margin 中绝对值中最大的,然后,和正 margin 值中最大的 margin 相加。
示例代码:
<div style="height:50px; margin-bottom:-50px; width:50px; ">A</div>
<div style="height:50px; margin-top:100px; width:50px; ">B</div>
示意图:
上面的例子最终的 margin 应该是 100 + (-50) = 50px。
4). 相邻的 margin 要一起参与计算,不得分步计算
要注意,相邻的元素不一定非要是兄弟节点,父子节点也可以,即使不是兄弟父子节点也可以相邻。
而且,在计算时,相邻的 margin 要一起参与计算,不得分步计算。
一个复杂的实例:
<div style="margin:50px 0; width:50px;">
<div style="margin:-60px 0;">
<div style="margin:150px 0;">A</div>
</div>
</div>
<div style="margin:-100px 0; width:50px;">
<div style="margin:-120px 0;">
<div style="margin:200px 0;">B</div>
</div>
</div>
错误的计算方式:算 A 和 B 之间的 margin,分别算 A 和其父元素的折叠,然后与其父元素的父元素的折叠,这个值算出来之后,应该是 90px。依此法算出 B 的为 80px;然后,A和B折叠,margin 为 90px。
请注意,多个 margin 相邻折叠成一个 margin,所以计算的时候,应该取所有相关的值一起计算,而不能分开分步来算。
以上例子中,A 和 B 之间的 margin 折叠产生的 margin,是6个相邻 margin 折叠的结果。将其 margin 值分为两组:
- 正值:50px,150px,200px
- 负值:-60px,-100px,-120px
根据有正有负时的计算规则,正值的最大值为 200px,负值中绝对值最大的是 -120px,所以,最终折叠后的 margin 应该是 200 + (-120) = 80px。
浮动元素、inline-block 元素、绝对定位元素的 margin 不会和垂直方向上其他元素的 margin 折叠
浮动元素的 margin 在垂直方向上也不会发生 margin 折叠,即使和它相邻的子元素也不会。
<div style="margin-bottom:50px; width:50px; height:50px; ">A</div>
<div style="margin-top:50px; width:100px; height:100px; float:left;">
<div style="margin-top:50px; ">B</div>
</div>
示意图:
两个绿色的块儿之间,相距100px,而若 B 和它的浮动包含块发生 margin 折叠的话,金色的条应该位于绿色块的最上方,显然,没有发生折叠。
创建了块级格式化上下文1的元素,不和它的子元素发生 margin 折叠
以 “overflow : hidden” 的元素为例:
<div style="margin-top:50px; width:100px; height:100px; overflow:hidden;"> <div style="margin-top:50px; ">B</div> </div>
若 B 和它的 "overflow:hidden" 包含块发生 margin 折叠的话,金色的条应该位于绿色块的最上方,否则,没有发生。
示意图:
元素自身的 margin-bottom 和 margin-top 相邻时也会折叠
自身 margin-bottom 和 margin-top 相邻,只能是自身内容为空,垂直方向上 border、padding 为 0。
示例:
<div style="border:1px solid red; width:100px;">
<div style="margin-top: 100px;margin-bottom: 50px;"></div>
</div>
以上代码运行后,我们讲得到的是红色边框的正方形,方框的宽高都应该是 100px,高度不应该是 150px。
示意图: