# 盒子模型

原文地址:https://web.dev/learn/css/box-model/

CSS 显示的每一个内容都是一个盒子。理解盒子模型是如何工作,是 CSS 的核心基础。

假设你有这么一段 HTML

<p>I am a paragraph of text that has a few words in it.</p>

然后为它写个CSS

p {
  width: 100px;
  height: 50px;
  padding: 20px;
  border: 1px solid;
}

内容会超出元素边界。而且它宽度其实是140px,而不是100px。为什么会这样?盒子模型是CSS核心基础,理解它是如何工作的,它是如何受到CSS其他方面影响的。重要的是,如何掌握它才能我们写出更可预测的CSS代码。

https://codepen.io/web-dot-dev/embed/WNRemxN

在写CSS或在写整个web时,有一个非常重要的事情,那就是CSS显示的所有内容都是一个个盒子。即使盒子使用了border-radius圆角属性让它看起来像个圆,或者放一些文本。唯一要记住就是:“它们都是盒子”。

# 内容和大小

译者注

  • extrinsic sizing 外部大小:即根据元素的上下文确定大小,而不考虑其内容。(比如直接给它设置一个width)
  • intrinsic sizing 内在大小:即根据元素内容来确定大小,而不考虑其上下文。(比如 min-contentmax-content)

参考:

盒子会根据不同的display 值会有不同的特性,设置尺寸和内容都会影响到它们。内容可以是更多的盒子(子元素)或纯文本内容。无论哪种方式,默认情况下,内容都会影响盒子的尺寸。

你可以使用外部大小来控制盒子大小,或者也可以让浏览器使用内在大小来决定。

为了快速区别它们的不同,让我们来看个例子:

https://codepen.io/web-dot-dev/embed/abpoMBL

该 Demo 里面有一个固定尺寸和粗描边的盒子,盒子里面写着 "CSS is awesome"。盒子设置了宽度,所以是它是外部大小,限制其内容的大小。但问题是这个 "awesome" 对这个盒子来说太大了,所以它溢出了父亲的边框(稍后会详细介绍)。为了防止这种溢出的一种方式就是不要给盒子设置宽度,或在这个例子中,设置width的值为min-content

译者注

原英文的解释不好理解,所以使用了zhangxinxu的原句:
这个min-content 宽度表示的并不是内部哪个宽度小就是哪个宽度,而是,采用内部元素最小宽度值最大的那个元素的宽度作为最终容器的宽度。

这就让盒子恰当的环绕 "CSS is awesome",完美!

让我们看一些更复杂的例子,看看不同大小对实际内容的影响:

https://codepen.io/web-dot-dev/embed/wvgwOJV

[这段太难翻译:备注下,后面再回过头看看] 通过切换内部大小开关,来看看使用外部大小你可以控制哪些,或由内容使用内部大小来控制尺寸。为了查看这两个不同尺寸类型的效果,我们在卡片下面添加了一些文字内容。当元素使用外部大小时,那么对于添加更多的元素内容,其实是有个约束让它不会溢出元素的边界。但当使用内部大小时,情况就不是这样的了。

默认情况下,元素设置widthheight都为400px。这两尺寸为元素内的所有内容提供了严格的界限,除非内容对于盒子来说太大,以至于导致溢出。你可以在那有朵花的图片下面添加更多的文字,来看看溢出效果。

关键术语:当内容对盒子来说太大了,我们称之为overflow(即溢出)。你可以使用overflow属性,来管理元素如何处理溢出内容。

当你切换到内部大小时,相当于你让浏览器依据盒子内容大小为你做出决定。这时内容想要溢出是非常困难的,因为我们的盒子会随着内容动态调整自己尺寸,而不是去调整内容尺寸。最重要的一点就是这是浏览器默认的,弹性的。尽管外部大小可以更好的控制外表,但在大多数情况下,内部大小提供了更大的灵活性。

# 盒子模型的区域

盒子是由多个不同的盒子模型区域组成,它们都有特定的作用。

The areas of the box model

盒子模型4个主要区域:内容盒子、内边距盒子、边框盒子 和 外边距盒子。

我们从内容盒子开始,它表示的是内容区域。正如你之前了解的:内容可以控制其父级大小,因此它通常是经常变化的。

内边距盒子(也可认为填充盒子)环绕内容盒子,它的空间由padding属性值决定。因为是填充在盒子里面,盒子的背景图是能在这个区域看到的。如果我们的盒子设置了溢出规则,例如overflow: autooverflow: scroll,那么滚动条也是会占用这个空间的。

https://codepen.io/web-dot-dev/embed/bGNmgGW

边框盒子环绕着内边距盒子,它的空间由border属性值决定。边框盒子是你能直观看到盒子范围的最大边缘。border属性通被用来给元素画个边框。

最后一个外边距盒子,是环绕着盒子的外空间,由margin属性值决定。像outlinebox-shadow属性也占据这个空间,因为它们是画在上层的,所以它们不会影响我们盒子的尺寸。你可以在盒子上每边设置outline-width200px,但盒子内的所有内容尺寸和边框尺寸等都不会受到影响。

https://codepen.io/web-dot-dev/embed/XWprGea

# 一个实用的例子

盒子模型可能比较难理解,所以让我们在这里使用一个例子来回顾一下我们学到的知识。

画廊

在这幅图中,你有三个相框并排安在墙上,并在图上写了一些和盒子模型相关的标签。

让我们详细分析一下这个例子:

  • 艺术品本身可以看成内容盒子
  • 位于边框和和艺术品之间的白色磨砂区域可以看成内边距盒子
  • 这件艺术品边框就是边框盒子了
  • 一件艺术品边框与另外一件艺术品边框之间的间距,可以认为是外边距盒子
  • 艺术品框架的光照投影(阴影),也属于外边距空间

# 盒子模型调试

浏览器开发者工具(DevTools)提供了所选盒子模型的可视化,这可以帮助你理解盒子模型的工作原理,更重要的是,它是如何影响你正在写的网页。

自己在浏览器试一试吧。

  1. 打开开发者工具 (opens new window)
  2. 选择一个元素 (opens new window)
  3. 显示盒子模型调速器(在Styles tab 底部,或在 Computed tab 下面)

# 掌控盒子模型

要想了解如何掌控盒子模型,那你得先知道浏览器做了什么。

译者注

user agent stylesheet 是指浏览器默认样式表,当用户没有写CSS时,浏览器会按照内置的样式表来渲染。但各种浏览器差异很大,所以为什么需要类似Normalize.css的项目

每个浏览器都给HTML文档提供了默认样式表,虽然每个浏览器默认样式差异很大,但至少它们提供了合理的默认值以使得内容更易阅读。它们定义了元素的外观和行为,当页面没有使用CSS时。在默认样式中,每个格子的display也都设置了默认值。例如,如果我们在正常文档流,div元素display默认值是blockli元素display默认值是list-item,而span元素display默认值是inline

给一个行内元素(inline)设置外边距,但其他元素是不会受它影响的。如果把它display设置为line-block就可以让其他元素受到它外边距的影响,而且这个元素还可继续保持内联元素的一些行为。一个块级元素(block)默认情况是填充整个它所在的横向内在空间。而inlineinline-block元素最大也只会和它内容一样大。

译者注

上面一段的翻译可能有问题,也可能是原文有问题。行内元素比如span(不可替换元素)的margin-topmargin-bottom 即使设置了,也不会对周围的元素产生影响。但 margin-leftmargin-right 是会对周围元素产生影响的。如果行内元素是img(可替换元素),那么 margin 4个值都会对周围元素有影响。padding同理。

除了理解浏览器默认样式是如何影响每一个盒子,还需要了解 box-sizing 属性。它告诉浏览器怎么计算盒子模型尺寸。默认情况,所有元素的默认样式是:box-sizing:content-box;

当元素设置了box-sizing:content-box,意味着当你给它设置尺寸时,比如widthheight,它们就是内容盒子的尺寸。当你再设置 paddingborder,它们将会作为额外的值添加到内容盒子的尺寸上。

译者注

这里我举两个例子:

  • divA 设置 box-sizing: content-box,然后 width 设置 100px,那么它的内容宽度就是 100px,然后你再设置 padding: 20px; border: 10px solid,那么整个 divA 的占据的宽度空间将是100+20+20+10+10 = 160px
  • divB 设置 box-sizing: border-box,然后 width 设置 100px,然后你再设置 padding: 20px; border: 10px solid,那么整个 divB 的占据的宽度空间还是 100px,但其内容盒子宽度是:100-20-20-10-10 =4 0px。 所以目前主流的设置盒子模型 box-sizingborder-box

这个好处是,在写页面时心智压力上会小一些,也更容易布局。文章下面也有讲。

/*小测试*/
/*你认为 .my-box 的实际宽度应该是 200px 还是 260px */
.my-box {
  width: 200px;
  border: 10px solid;
  padding: 20px;
}

这个盒子的实际宽度将是260px。由于CSS默认设置box-sizing: content-box,所以它的实际宽度是由内容宽度、padding两边尺寸和border两边尺寸之和算出来的。所以就是200px的内容宽度 + 40px的padding值 + 20px的border值,等于260px。

不过,你可以用 border-box 来修改盒子模型:

.my-box {
  box-sizing: border-box;
  width: 200px;
  border: 10px solid;
  padding: 20px;
}

border-box 盒子模型,就是告诉 CSS 将实际的宽度应用到边框盒子上,而不是内容盒子。这意味着我们的borderpadding将会被推入。当设置 .my-box200px宽度时,那它实际上就是200px宽度。

我们通过下面的 demo 来看看其工作原理。请注意,当你切换box-sizing值时,它将会通过蓝色背景来显示哪些CSS被应用在盒子内。

https://codepen.io/web-dot-dev/embed/oNBvVpM

*,
*::before,
*::after {
  box-sizing: border-box;
}

上面一小段 CSS 代码,是让文档内的所有的元素,包括::before::after 等伪类元素都应用 box-sizing: border-box。这意味着每个元素都将使用这个新的盒子模型。

因为 border-box 盒子模型更可预测,开发人员经常会添加此规则到重置样式表中,比如这个 (opens new window)

# 相关资源

# 各浏览器默认样式