TT Lab
开始
学习 学习路径 课程

CSS — 后写的规则获胜

300px 为什么不是 300px

在 TT Lab 中继续学习

一句话总结

width: 300px 在默认设置下指的是内容区域的宽度,而不是盒子的宽度。而且上下外边距不会相加,而是会折叠。布局错位的事故,大部分都来自这两点。

为什么需要它

“两个栏各放 50%,结果换行了。”“加了内边距之后,卡片从网格里溢出来了。”“上面的元素设了 margin-bottom: 20px,下面的元素设了 margin-top: 30px,间距却不是 50px,而是 30px。”

这三种情况都不是因为不懂 CSS,而是不了解模型就填了数值。而且这三种情况,用眼睛看除了“怎么会这样”之外什么也看不出来。只要知道计算规则,当场就能得出答案。

工作原理——盒子的四层

┌─────────── margin ───────────┐
│ ┌───────── border ─────────┐ │
│ │ ┌─────── padding ──────┐ │ │
│ │ │      content         │ │ │
│ │ └──────────────────────┘ │ │
│ └──────────────────────────┘ │
└──────────────────────────────┘

这是 CSS 2 规范的盒模型 定义的四层。在默认值 box-sizing: content-box 下,width 只指最里面的 content。所以:

.card { width: 300px; padding: 20px; border: 1px solid; }

这张卡片占据的宽度是 300 + 20 + 20 + 1 + 1 = 342px。如果父元素是 300px,就会溢出。

设置 box-sizing: border-box 之后,width 就变成了包含 border 在内的值。上面的卡片正好是 300px,内容区域缩小到 258px。MDN 的 box-sizing 文档 对比了这两种模式。

所以几乎所有项目都会在最上面放这三行。

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

无论在哪种模式下,margin 都不包含在 width 中。只有这一点没有例外。

外边距折叠——不是相加,而是合并

上下相邻的外边距不会相加,而是变成两者中较大的那一个。 MDN 的外边距折叠文档 整理了三种情况。

  1. 相邻的兄弟元素——上面元素的下外边距与下面元素的上外边距合并
  2. 父元素与第一个或最后一个子元素——如果中间既没有边框也没有内边距,子元素的外边距就会漏到父元素外面
  3. 空元素——它自己的上下外边距会互相合并

第二种最常让人困惑。为了在父盒子内部留出间距,给子元素设置了 margin-top,结果父元素整个被推到下面去了。如果有背景色,这个颜色没有跟着下来,看起来就更奇怪。

阻止折叠的办法是在中间放点什么。给父元素加一行 padding-top、一行 border-top,或者 display: flow-root。创建新的格式化上下文(BFC)之后,外边距就被关在里面了。

而且水平外边距绝不会折叠。 左右两侧始终是相加的。

在 flex 和 grid 中没有折叠

外边距折叠是常规流(block 格式化上下文)的规则。在 flex 项目之间和 grid 项目之间不会发生。

所以如今的布局中,碰到折叠的机会变少了。取而代之的是 gap。gap 只加在项目之间,外侧不加,所以“只有最后一个元素要去掉外边距”的那条规则就完全不需要了。

.list { display: flex; flex-direction: column; gap: 16px; }

这一行替代了 .list > * + * { margin-top: 16px }。

数值以哪里为基准来度量

百分比以包含块的宽度为基准。这里有一个陷阱——padding-top 和 margin-top 的百分比也是以宽度为基准计算的。 不是高度。在制作保持宽高比的盒子时,使用 padding-top: 56.25% 的老办法就是由此而来(现在有了 aspect-ratio)。

而且 min-width 和 max-width 是在 width 之后应用的。同时使用 width: 100% 和 max-width: 640px,就会得到不超过 640px 的流式盒子。CSS Values 定义了单位和计算顺序。

在现场相遇的样子

下面记录这个服务中实际发生过的两件事。

第一,只有正文溢出屏幕的那件事。body 被设成 flex column,又给正文设置了 margin: 0 auto,但 flex 子元素一旦带有自动外边距,拉伸(stretch)就会被关闭。当一个很宽的代码块进来时,只有正文被撑到那个宽度,与顶部栏错开了。明确写上 width: 100% 之后就修好了。

第二,卡片网格一行只放下两个的那件事。设置了 width: 33.33% 和 padding: 16px,而 box-sizing 是默认值。三个加起来超过了 100%,于是换行了。加一行 border-box 就解决了。

这两件事,如果不了解计算规则,就会以“CSS 很奇怪”告终;如果了解,在开发者工具里看一眼计算出来的值就结束了。

下一项测验要确认什么

会确认在 box-sizing 两种模式下盒子实际占据的宽度、外边距折叠发生的三种情况以及阻止的方法,还有百分比以什么为基准来度量。