Skip to content

通过前面的内容,读者应该能清楚知道单个盒子的规则是什么样的,一个网页中不可能很自由一个盒子,肯定是由非常多的盒子共同组成的,那这多个盒子的排列规则就是视觉格式化模型(布局规则),视觉格式化模型大体将页面上盒子的排列规则分为三种方式,常规流、浮动、定位。

本文档先介绍第一种排列方式,常规流,常规流有很多别名,也可以称作文档流、普通文档流、常规文档流。这些指的都是常规流。这名字一听就很常规对吧,所以其实所有的元素,默认都是常规流布局。

既然常规流是多个元素之间的排列规则,而元素默认就是常规流,所以其实我们前面接触到的多个元素的排列规则都是常规流,我们前面是不是说过,块盒需要独占一行,行盒需要从左到右依次水平排列,这其实就是常规流的总体规则。

包含块 (containing block)

每个元素都有一个包含块,我们所说的包含块是指这个元素是基于哪个元素排列的。在绝大多数情况下,一个元素的包含块都是指其父元素的 content-box。什么意思呢,这里就以常规流为例,在常规流中,一个元素的包含块一定是其父元素的 content-box,并且我们知道块盒会独占一行,这里的独占一行,指的就是相对于其父元素的 content-box 而言是独占一行的,即占满其父元素的 content-box 的一行,而不会占整个网页的一行。

接下来介绍一些常规流的具体规则,行盒其实没什么好说的,来介绍块盒的具体规则。

一个块盒的 margin-box,必须等于其包含块的宽度,这里的 margin-box 包含内容区+内边距+边框+外边距。正是由于在常规流中规定块盒的 margin-box 区域必须占满其包含块的一整行,所以在常规流中的块盒才有了独占一行的显示效果。

html
<!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>
        * {
            margin: 0;
            padding: 0;
        }
        .parent {
            height: 100px;
            border: 1px solid;
            padding: 10px;
        }
        .child {
            height: 50px;
            border: 1px solid;
        }
    </style>
</head>
<body>
    <div class="parent">
        <div class="child"></div>
    </div>
</body>
</html>

以上为例,HTML 由 parent 和 child 两个盒子组成,child 并没有设置宽度,这里默认为常规流,此时 child 盒子的包含块为其父元素的 content-box 区域,即按照我们前面的规则, child 盒子作为一个块盒,margin-box 的宽度必须占满其父元素 content-box 区域的一行。

![Pasted image 20240211183208](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240211183208.png)

这里其实很好验证,按照我们前面的说法,此时 parent 元素的 content-box 部分就是 child 元素 margin-box 的宽度。即必须满足 margin-box 的 width = 包含块的 width 这样一个恒等式

这个例子中,border 的默认值是 0,padding 的默认值也是 0,margin 的默认值也是 0。而 width 的默认值为 auto,这个 auto 值就是让浏览器自动选择宽度,由于 border, padding, margin 的默认值都是 0,为了满足上面的恒等式,就必须让 width 把所有空间吸收掉,所以宽度看起来默认是 100%。

auto 这个取值其实就是自动的意思,让浏览器根据一定规则自动选择一个值。

接下来我们限制宽度看一下效果

![Pasted image 20240211195921](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240211195921.png)

如图,可以看到,如果给盒子固定了宽度,此时就有问题了,此时 margin、padding、border 默认值都是 0,并且 width 被限制死了,此时 margin-box 的宽度相当于就已经固定死了,但我们要求 margin-box 的宽度必须等于包含块的宽度,剩下的宽度怎么办呢,就会强制改变 margin-right,相当于让 margin-right 变成 auto,让 margin-right 把包含块一行剩下的空间全部吸收。

如果一个块盒将宽度固定, margin-left 和 margin-right 都设置为 auto,要保证独占一行,即 margin-box 的宽度要等于包含块宽度,此时左外边距和右外边距都会自动吸收掉包含块的剩余宽度,并且是一边吸收一半,这就实现了块盒水平居中的效果,在实际开发中,margin:0 auto; 是很常见的块盒居中写法。

接下来看下面的代码

html
<!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>
        * {
            margin: 0;
            padding: 0;
        }
        .parent {
            height: 100px;
            border: 1px solid;
            width: 500px;
            margin: 0 auto;
            padding: 10px;
        }
        .child {
            height: 50px;
            border: 1px solid;
            margin-left: -100px;
        }
    </style>
</head>
<body>
    <div class="parent">
        <div class="child"></div>
    </div>
</body>
</html>

效果如下

![Pasted image 20240211210924](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240211210924.png)

接下来来分析一下,还是那个恒等式,在常规流中,块盒的 margin-box 宽度必须等于包含块的宽度。或者可以说其 margin-box 宽度必须等于其父元素的 content-box 宽度。其父元素 content-box 宽度为 500 像素,即 child 盒子的 margin-box (margin+border+padding+content) 宽度一定是 500 像素。我们知道左右 border 都有 1 像素,加起来 2 像素,左边的 margin 为-100 像素,右边的 margin 为 0,而 child 的 padding 也为 0,而 width 默认为 auto,所以就由 width 来自动吸收剩余宽度,所以我们得出 child 的 width为 500-(-100)-2=598 像素。

![Pasted image 20240211211506](/notes-assets/01.HTML&CSS/2.CSS基础/assets/Pasted image 20240211211506.png)

前面提到过,auto 取值就是按照一定规则自动选择一个值,这里的规则其实就是那个恒等式对吧,即如果 margin, padding, border 都固定了,而 width 让他是默认值 auto,要让 margin-box 的宽度和包含块的宽度一致,width 的 auto 就会起作用,让 width 直接占满剩余空间。但如果我把左右 margin 和 width 都设置为 auto,此时 margin 是抢不过 width 的,也就是说 margin 此时还是 0,会优先使用 width 的 auto 填充剩余空间。

百分比取值

padding、width、margin 可以取值为百分比,这些所有的百分比都相对于包含块的宽度,例如设置 margin-top 为 50%,则 margin-top 的值就是其包含块的宽度为 50%。需要注意,这里是基于包含块的宽度,而不是包含块的 width 属性,包含块在常规流布局中始终是其父元素的 content-box ,但 width 属性并不总是设置 content-box 的宽度,这里我们可以通过 box-sizing 属性来修改。 除此之外,height 也可以设置为百分比,但并不相对于包含块的宽度,而是相对于包含块的高度。这里会存在一个问题,如果包含块没有设置高度,高度默认为 auto,在这种情况下,高度设置百分比是完全失效的。即只有在包含块有了固定高度的情况下,设置百分比高度才会生效。同时相对的也是包含块的高度,而非父元素的 height 属性值,尽管大多数情况下父元素的 height 属性值都是包含块的高度。

当 width 被设置为百分比之后,宽度会随着父元素进行变化,而最外层的其实是窗口尺寸,即如果将最外层容器设置为百分比宽度,那么就是 body 宽度的百分比,而 body 的宽度是跟着窗口来变得,此时如果窗口缩的很小或很大,可能会让我们的网页出现一些问题,基于此可以给 body 元素设置 min-width 属性设置其最小宽度,当窗口小于这个最小宽度之后会出现横向滚动条。同理还有 max-width。

外边距折叠

在常规流中,垂直方向上相邻外边距会进行折叠,如下图所示:
recording
这里可以很明显地看出,我给这两个盒子都设置了外边距,但上面盒子的 margin-bottom 和下面盒子的 margin-top 被折叠成了同一个区域了,即本来按道理说,上面盒子有一个 20 像素的下外边距,下面盒子有一个 20 像素的上外边距,这两个盒子的距离应该是 40 像素,但实际上这两个相邻外边距被折叠为同一块区域,二者距离变成了 20 像素。这个问题也称为 margin 合并。外边距的折叠规则就是取最大值,例如上盒子的 margin-bottom 为 x,下盒子的 margin-top 为 y,那么这两个盒子的实际距离就是 x 和 y 的最大值。

除了相邻元素的上下外边距会相邻之外,父子元素的上 margin 经常也会相邻,如下图所示。
![recording 1](/notes-assets/01.HTML&CSS/2.CSS基础/assets/recording 1.gif)
可以看到,这里我改变的是子元素的 margin-top 值,按道理说子元素的 margin-top 应该是距离其父元素的上边缘的距离,但实际上的效果是父元素的位置是跟着子元素来动的,这是由于子元素的 margin-top 和父元素的 margin-top 是挨在一起的,所以被折叠成同一个区域了,所以当我们设置子元素的 margin-top 的时候,从显示效果来看相当于是设置父元素的 margin-top。这个问题也被称为 margin 塌陷。由于父元素和子元素的 margin-top 是挨在一起的,所以这两个 margin-top 会被合并为同一个区域,折叠的方式其实也是取最大值,例如父元素的 margin-top 为 x,子元素的 margin-top 为 y,实际的 margin-top 就是 x 和 y 的最大值。当然由于折叠成了同一个区域,父元素也就跟着子元素往下动了,这往往不是我们想要的效果,要解决这个问题,其实就是用东西把这两个 margin 隔开,比如可以设置父元素的 border,比如可以设置父元素的 padding,都可以将这两个 margin 隔开,就可以解决这个父子元素之间的折叠问题。

外边距折叠并不是 bug,在打印排版的时候让外边距折叠其实更加符合我们的预期。

以笔记为镜,可以知得失