理解css2.1中的block-and-inline layout

这几个概念等价:block-and-inline layout normal flow flow layout

flow layout就是所谓的流体布局,它分为垂直和水平两个方向的布局上下文,垂直方向上的布局上下文称为:block formatting context,简称BFC;水平方向上的布局上下文称为inline formatting context,简称IFCboxesflow layout里面,要么处于BFC当中,要么处于IFC当中,不可能同时位于两个上下文。具体来说:block-levelbox位于BFCinline-levelbox位于IFC

block formatting context

网页默认就有一个BFC,一开始的内容都是布局在这个BFC里面的。 有很多方法可以让一个box新建BFC,比如绝对或固定定位 floats display: inline-block | table-cell | table-caption 非visible值的overflow

BFC里面,只有block-levelbox,不会出现inline box,如果同时有block boxinline box,则会在inline box外部包裹一层anonymous block-level box。这些box从它的containing block顶部边缘开始,从上到下依次排列布局。两个相邻的box之间的间隙是由它们的外边距决定的,外边距满足条件时会触发外边距合并。

如果一个block-levelbox未创建一个新的BFC,则它的children就会跟自己一起,布局在相同的BFC里面;如果它创建了BFC,则它的children会部署在自己新建的BFC里面,与自己所在的BFC是分离的。 只有位于相同BFC当中的box,才会发生特定的flow layout行为,否则不会。比如外边距合并这种行为就只能发生在相同的BFC当中。

看下面这个例子:

1
2
3
4
5
6
7
8
9
10
<body>
<div class="box1" style="margin-top: 30px">
<div class="ovh">
<div class="child1" style="margin-top: 10px">123</div>
<div class="child2">abc</div>
</div>
456
</div>
<div class="box2" >
</body>

上面的例子,没有创建任何新的BFC,所以所有的box全部在相同的BFC中布局,具体如下:

  • div.box1div.box2这两个box,按照先后顺序,从body的顶部边缘开始依次布局下来
  • 456这个文本外面,会生成一个anonymous block box
  • div.ovh456 anonymous block box这两个box,按照先后顺序,从div.box1的顶部边缘依次布局下来
  • div.child1div.child2这两个box,按照先后顺序,从div.ovh的顶部边缘开始依次布局下来
  • div.child1div.box1top margin会发生合并

如果代码改动一下:

1
2
3
4
5
6
7
8
9
10
<body>
<div class="box1" style="margin-top: 30px">
<div class="ovh" style="overflow:hidden">
<div class="child1" style="margin-top: 10px">123</div>
<div class="child2">abc</div>
</div>
456
</div>
<div class="box2" >
</body>

  • 因为overflow:hidden的作用,div.ovh现在会新建BFC
  • div.child1div.child2这两个box,按照先后顺序,从div.ovh的顶部边缘开始依次布局下来;但此时它俩所属的布局上下文是div.ovh新建的那个,而不是div.ovh自身所处的那个;
  • body div.box1 div.ovh div.box2 456 anonymouse box仍然处于相同的BFC当中
  • 因为div.ovh所新建的BFC的作用,div.child1div.box1top margin不会再发生合并

BFC中还有一个特性,就是box的左边的边缘,会与containing block的左边边缘对齐。这样的话,加上boxwidth初始值始终是auto,就会让box形成一个水平方向上充满containing block的布局效果,这就是box所谓的流体特性。 注意width: autowidth: 100%不是一样的,这就是为啥有的时候子元素设置width: 100%,反而不能自动充满父元素,可能会超出的原因,比如这个代码:

1
2
3
<div style="width: 300px; padding: 0 30px;">
<div style="width: 100%"></div>
</div>

父级div最终的宽度会变为360,而不是300,因为子div的宽度设置为100%,所以它的宽度也是300,一下子就把父div撑开了,width: 100%导致子div失去了自动在水平方向充满父div内容区的流体效果。

BFC有两个布局要点:

  1. box的顶部边缘对齐containing block的顶部。
  2. box的左边对齐containing block的左边。

float box inline block boxabsolutely positioned box这类BFCbox会导致它失去在水平方向上的流体布局特性,宽度不再自动充满containing block的宽度,而是收缩到自己的内容实际填充宽度,这就是所谓的包裹性。

inline formatting context

当一个block-levelbox里面只有inline box的时候,它会创建一个inline formatting context来布局这些inline内容。inline box一定是在一个IFC中布局的,它的children也会跟它一起布局在相同的IFC当中。

IFC当中,inline box也是从containing block的顶部开始布局的。水平方向上的margin border padding都会起作用。这些inline box在垂直方向上有多种对齐方式,可能是按照box的顶部边缘对齐、可能是底部边缘对齐、也可能是按照文本基线对齐。在IFC当中,每一行都是一个不可见矩形区域,来包裹这些inline box,这个矩形区域称为line box

line box默认的宽度是跟containing block的宽度一样的,但是当line box遇到float元素后,line box会自动收缩自己的宽度,以便line box不跟float发生重叠,形成了line box环绕float box的效果。line box的高度根据line-height属性的计算规则有关,需要学习其它规范内容。

line box的高度始终大于它里面任意一个inline box的高度,甚至会出现line box的高度比它里面最高的box的高度还要高。当某个box的高度低于line box的高度时,这个box在这一行内垂直方向上的对齐方式是由vertical-align属性决定的。当inline boxs用一个line box排不下的时候,它们会被分割到垂直堆叠的多个line box中来布局,因为line box的宽度最大就是containing block的宽度。一个inline formatting context实际上由垂直堆叠的多个line box布局出来的,这些堆叠的line box不会发生重叠,同时它们之间在默认情况下不会出现间隙。

同一个IFC当中的line box,高度不一定相同,比如某个line box内有张图片,另一个没有。默认情况下line box的左边与containing block的左边对齐,line box的右边与containing block的右边对齐;但是当float出现后,部分line box的宽度会变窄,以便不与float box发生重叠。line boxfloat box的这种环绕行为,以及清除浮动的行为,都只会在同一个BFC当中发生。 float会让box脱离BFCIFC的这种文档流布局,但是不会脱离BFC,所以float与相应的line box还是处于同一个BFC,它们才能发生一些特定行为。 假如不想让某个box里面的line box与这个box所在的BFC中的float box发生特定行为,只需要把这个box变为BFC即可。

当一个line box内所有的inline box的总宽度小于line box的宽度时,这些inline box在水平方向上的对齐方式,就由text-align属性来决定。如果某个inline box在一个line box内排不下,它就会被分割为多个box,然后布局到下一个line box当中。但是当一个inline box不允许被分割时,比如设置了white-space: nowrap | pre,那这个inline box就会溢出当前它所在的line box

当一个inline box被分割时,margin padding border都不会有分割的视觉效果,就是margin padding border看起来仍然会是连续的。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>

<style type="text/css">
.container {
width: 150px;
margin: 0 auto;
background: #ccc;
}

.target {
border: 10px solid red;
margin: 0 20px;
padding: 0 20px;
background: blue;
color: #fff;
border-top: 0;
border-bottom: 0;
}
</style>
</head>

<body>
<div class="container">
Several <em class="target">emphasized words</em> appear
<strong>in this</strong> sentence, dear.</div>
</body>

</html>


其实就是说虽然inline box被分割为多个box了,但是它们从整体上是满足一个boxbox model的,所以把它当成一个box看待即可。

line boxIFC为了包含inline-level的内容创建的。

Line boxes that contain no text, no preserved white space, no inline elements with non-zero margins, padding, or borders, and no other in-flow content (such as images, inline blocks or inline tables), and do not end with a preserved newline must be treated as zero-height line boxes for the purposes of determining the positions of any elements inside of them
不含文本,没有preserved white space,没有带marign、或带border或带padding的内联元素,没有其它未脱离文档流的布局内容(比如图片、内联block,内联table),并且没有换行符的此类line box,必须被当做是zero-height line box(0高的line box)来处理,这是为了这些line box中包含的元素定位的目的。比如某个div内包含一个inline-blockdiv,此时这个子div所属于的line box肯定不是zero-height line box,但是如果把inline-blockdiv,设置了绝对定位之后呢?它就会脱离它原来所在的line box,原来的line box就变为zero-height line box了,这个line box虽然高度为0,但是是对于子div的定位是有作用的。但是另一方面,zero-height line box在其它的场合中,必须被当成不存在的line box处理。