# 盒模型

CSS 基础框盒模型(CSS basic box model) 是 CSS 规范的一个模块,常被直译为盒子模型、盒模型或框模型。

它定义了一种长方形的盒子,包括它们各自的内边距(padding)与外边距(margin ),并根据视觉格式化模型来生成元素,对其进行布置、编排、布局(layout)。

# 基础概念

当对一个文档进行布局的时候,浏览器的渲染引擎会根据盒模型,将所有元素表示为一个个矩形的盒子。

每个盒子由四个部分(或称区域)组成,其效用由它们各自的边界(Edge)所定义。与盒子的四个组成区域相对应,每个盒子有四个边界:

boxmodel
  • 内容边界(Content Edge):由内容边界限制,容纳着元素的“真实”内容,例如文本、图像等,它的尺寸就是 content-box 的尺寸。
  • 内边距边界(Padding Edge):由内边距边界限制,扩展自内容区域,负责延伸内容区域的背景,填充元素中内容与边框的间距。它的尺寸就是 padding-box 的尺寸。
  • 边框边界(Border Edge):由边框边界限制,扩展自内边距区域,是容纳边框的区域。其尺寸就是 border-box 的尺寸。
  • 外边框边界(Margin Edge):由外边距边界限制,用空白区域扩展边框区域,以分开相邻的元素。它的尺寸为 margin-box 的尺寸。

同几者密切相关的 CSS 属性包括 box-sizingbackground-clip

box-sizing 属性定义了 user agent 应该如何计算一个元素的总宽度和总高度,可能的值主要包括 content-box(默认)和 border-box

  • 在默认情况(标准盒模型)下,widthheight 只包括内容的宽和高, 不包括边框(border)、内边距(padding)和边距(margin)。
  • 而后者(IE 模型)的 widthheight 属性则包括内容,内边距和边框,但不包括外边距。
W3C_and_Internet_Explorer_box_models

对于 background-clip 属性,它被用来设置元素的背景(背景图片或颜色)是否延伸到边框下面。可能的值主要包括 border-box(默认)、padding-boxcontent-box

  • 默认情况下(border-box),背景延伸至边框外沿,且在边框下层。
  • 当设置为 padding-box 时,背景延伸至内边距外沿。
  • 而对于 content-box,背景被裁剪至内容区外沿。

# 规定元素的盒类型

每个元素都具有默认的盒子模型,但我们仍然可以显式的声明或改变一个元素的盒模型。

当我们把 CSS 属性 display 设置为 blocklist-itemtable 等时就会使元素变为块级元素,每个块级元素生成一个主块级盒来包含子级盒。

对应的,每个行级元素也会生成一个行级盒,行级盒可分布于多行中。当我们把 CSS 属性 display 设置为 inlineinline-block 等时就会使元素变为行级元素。

<style>
  p {
    width: 50em;
    padding: 1em;
    line-height: 2;
    background-color: #ccf;
    border: 2px solid #00f;
  }

  span {
    margin-top: 1px;
    padding-top: 1px;
    background-color: #fcc;
    border: 2px solid #f00;
  }
</style>

<p>
  <span>
    Learn to Code HTML & CSS is a simple and comprehensive guide dedicated to helping beginners
    learn HTML and CSS. Outlining the fundamentals, this guide works through all common elements of
    front-end design and development.
  </span>
</p>

运行上面的例子,可以发现由 span 标签产生的行级盒子在内容过多时产生了换行,但只在第一行有左边框,在最后一行有右边框,这正是因为行级盒子内容过多时产生了自动换行,而对于换行后的几行内容仍然属于一个行级盒子。

也正是由于上面的特性,我们在对多行的行级盒子设置 margin-left 时会发现只对第一行有效。

另外,行级盒子的 margin-topmargin-bottom 不会产生效果,其 padding-toppadding-bottom 也不会影响布局(不会撑开父元素)。

TIP

display 设置为 inline-block 时,此元素会生成一个行级盒,所以会在行内显示,而同时又为其内容产生了块级盒,所以除了在行内显示外,它表现出的确是块级元素的特征。

# 盒子的嵌套

在块级盒子中:

  • 可以包含多个子块级盒子。
  • 可以包含多个子行级盒子。
  • 其中不在行级元素中的文字,会生成匿名的行级盒。
  • 其中不能同时存在块级和行级盒子,遇到此种情况,将会生成匿名的块级盒子来包裹行级盒。

在行级盒子中:

  • 可以包含行级盒子。
  • 可以包含一个块级盒子,其中与块级盒子并列的行级盒子会被匿名的块级盒子包裹。

# 外边距合并

块级元素的上外边距和下外边距有时会合并(或折叠)为一个外边距,其大小取其中的最大者,这种行为称为外边距折叠(margin collapsing),有时也称为外边距合并。

外边距的折叠通常发生在下面三种情况:

  • 相邻元素之间

    相邻元素之间的外边距会折叠(除非后一个元素清除之前的浮动)。

  • 父元素与其第一个或最后一个子元素之间

    如果在父元素与其第一个子元素之间不存在边框、内边距、行内内容,也没有创建块格式化上下文、或者清除浮动将两者的 margin-top 分开;或者在父元素与其最后一个子元素之间不存在边框、内边距、行内内容、heightmin-heightmax-height 将两者的 margin-bottom 分开,那么这两对外边距之间会产生折叠。此时子元素的外边距会“溢出”到父元素的外面。

  • 空的块级元素

    如果一个块级元素中不包含任何内容,并且在其 margin-topmargin-bottom 之间没有边框、内边距、行内内容、heightmin-height 将两者分开,则该元素的上下外边距会折叠。

对外边距的含义,可以简单的理解为规定了元素外多大区域内不能摆放其它盒子,这对于理解外边距合并很有用。

以相邻元素为例:

.top {
  margin-bottom: 20px;
}

.bottom {
  margin-top: 10px;
}
<body>
  <div class="top">top</div>
  <div class="bottom">bottom</div>
</body>

此时,因为上面的元素规定其下方 20px 内不允许放其它的盒子,下面的元素规定其上方 10px 内不允许放置其它盒子,所以两者之间只要保留 20px 的距离就同时满足两者的条件了,因此产生了折叠。

另外,还有一些值得注意的地方:

  • 浮动元素和绝对定位元素的外边距不会折叠。
  • 即使某一外边距为 0,这些规则仍然适用。所以第一个或最后一个子元素的外边距可能会 “溢出” 到父元素的外面。
  • 如果参与折叠的外边距中包含负值,折叠后的外边距的值为最大的正边距与最小的负边距(即绝对值最大的负边距)的和。
  • 如果所有参与折叠的外边距都为负,折叠后的外边距的值为最小的负边距的值。这一规则适用于相邻元素和嵌套元素。

# 块格式化上下文(BFC)

块格式化上下文(Block Formatting Context,BFC) 是 Web 页面可视化 CSS 渲染的一部分,是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。

常见的产生块级上下文的方式包括:

  • 浮动元素(元素的 float 不是 none)。
  • 绝对定位元素(元素的 positionabsolutefixed)。
  • overflow 值不为 visible 的块元素。
  • display 值为 inline-blocktable-celltable-captionflexgridflow-root 的元素。

由于外边距折叠(Margin collapsing)只会发生在属于同一 BFC 的块级元素之间,因此我们可以通过创建 BFC 来解决外边距合并的问题。

.generate-bfc {
  overflow: hidden;
}

.top {
  margin-bottom: 20px;
}

.bottom {
  margin-top: 10px;
}
<body>
  <div class="generate-bfc">
    <div class="top">top</div>
  </div>

  <div class="bottom">bottom</div>
</body>

因为,块格式化上下文包含的是创建它的元素内部的所有内容,所以我们在 .top 元素的父元素上产生了 BFC,此时两者的外边距将不会产生折叠。

另外,浮动定位和清除浮动时只会应用于同一个 BFC 内的元素,所以利用 BFC 解决由浮动产生的高度塌陷也很简单,只需要在浮动元素的父元素上创建 BFC 就可以让让浮动元素也参与高度计算。

# 参考资料