# 元素在页面上到底占多大：content、padding、border、margin 与 box-sizing

四层盒子各自算在哪里、默认 width 为什么不止 200px，以及 border-box 改变了什么

> CSS 基础 · 盒模型与尺寸计算 · 约 5 分钟 · 09 月 28 日

## 本篇要点

1. 每个元素在排版时都是一个矩形盒子，由内到外是内容区、内边距、边框、外边距四层，四层是叠加关系，说清一个盒子的大小必须指明是哪一层。
2. 盒子可见的宽度到边框外沿为止，而它实际占用、把邻居推开的范围要算到外边距外沿，这是两个不同的数值。
3. `box-sizing` 的默认值是 `content-box`：`width`、`height` 只作用在内容区上，内边距和边框之后另加。所以 `width: 160px; padding: 20px; border: 8px solid` 的盒子实际上宽 216px、高 136px，两个方向都要乘以 2。
4. `box-sizing: border-box` 让 `width` 表示内容区、内边距、边框合起来的外沿宽度；同一个 160px 的盒子改成 border-box 后，内容区被压缩到 104px，对外的尺寸保持不变。
5. 内容区最小被压到 0，不能为负：当内边距加边框已经超过指定的宽度，盒子会比写的更宽。
6. 外边距永远不在 `width` 内，两种 `box-sizing` 都一样，它在盒子尺寸算完后才加在四周。
7. `table`、`select`、`button` 和按钮、勾选形的 `input` 默认就是 `border-box`，普通文本框的 `input` 仍是 `content-box`，所以设 `width` 时含义和 `div` 不同。
8. `box-sizing` 本身不继承，全局统一的常用写法是给 `html` 设 `border-box`，再让 `*`、`*::before`、`*::after` 用 `inherit` 继承下来。
9. 这套规则完整适用于块级盒子；行内元素不靠 `width`、`height` 控制大小，那属于正常流与 `display` 的问题。

---

上一篇讲的是样式怎么落到元素上：选择器命中谁、冲突时谁赢。现在假设你已经拿到了想要的那条声明——给一个盒子写了 `width: 200px`。打开页面量一下，屏幕上它却有 240px 宽。

差的 40px 不是浏览器算错了，而是 `width` 在默认情况下根本不管整个盒子。要解释这件事，得先看清一个元素在 CSS 里究竟是什么形状。

## 每个元素都是一个四层的矩形盒子

浏览器排版时，把每个元素表示成一个矩形盒子，由内到外分成四个区域，每个区域有自己的边界 [1]：

- **内容区**（content）：文字、图片真正放置的地方。默认情况下 `width`、`height` 量的就是这一块。
- **内边距**（padding）：内容和边框之间的空白，用 `padding` 设置。它在盒子内部，背景色会铺满它，所以看起来像是盒子的一部分。
- **边框**（border）：包在内边距外面，用 `border` 设置。
- **外边距**（margin）：盒子最外层的空白，用来把相邻元素推开，用 `margin` 设置。

![CSS 盒模型示意：内容区、内边距、边框、外边距由内到外四层](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Box_model/box-model.png)

四层是**叠加**的关系，不是四选一。所以“这个元素占多大空间”有两个不同的答案：盒子自身可见的宽度到边框外沿为止，而它实际占用、把邻居推开的范围，要一直算到外边距外沿。说清大小，必须指明是哪一层。

## 默认的 width 量的是内容区

`box-sizing` 的初始值（也就是默认值）是 `content-box`。在这个模式下，你写的 `width`、`height`，以及 `min-width`、`max-width` 这些同类属性，作用在**内容区**上，内边距和边框之后再往上加 [2]。

看一个具体例子：

```css
.box {
  width: 160px;
  height: 80px;
  padding: 20px;
  border: 8px solid red;
}
```

按默认的 `content-box` 算：

- 内容区宽 $160$px；
- 左右各加 $20$px 内边距，共 $40$px；
- 左右各加 $8$px 边框，共 $16$px；
- 屏幕上盒子的实际宽度是 $160 + 2\times 20 + 2\times 8 = 216$px。

高度同理，实际是 $80 + 2\times 20 + 2\times 8 = 136$px。这组数字来自 MDN 的官方示例 [2]。回到开头的困惑：`width: 200px` 再加上 `padding: 20px`，正好多出 40px，盒子就变成了 240px。

注意计算里的最后一步：`width` 只写了一次，但内边距和边框都是左右各一份，所以要乘以 2。忘了乘 2 是最常见的算错方式。

## box-sizing: border-box 改变了什么

每次都要心算着加上内边距和边框很麻烦，`box-sizing: border-box` 就是把这个加法交给浏览器。它让 `width` 表示的正好是盒子对外的可见宽度，也就是内容区、内边距、边框三者合起来的外沿（border box），但不含外边距 [2]。

同一个盒子改成 `border-box` 之后：

- 总宽仍然是 $160$px；
- 内容区宽变成 $160 - 2\times 20 - 2\times 8 = 104$px；
- 总高 $80$px，内容区高变成 $80 - 2\times 20 - 2\times 8 = 24$px。

区别在这里最清楚：内边距和边框不再是“加上去”，而是从你给的那个数字里**吃掉**一块。盒子对外的尺寸变成了你写多少就是多少，代价是内容区被压小。

## 两条不能忘的边界

第一条：**内容区不能是负数，最小被压到 0**。如果内边距和边框加起来已经超过你指定的宽度，盒子会变得比你写的更宽 [4]。比如指定 `width: 100px; box-sizing: border-box; padding: 0 60px;`，左右内边距一共 120px，已经超过 100px，浏览器只能把内容区压成 0，盒子实际宽 120px。所以 `border-box` 并不能把元素压到任意小。

第二条：**外边距永远不在 `width` 里面**。不管用哪种 `box-sizing`，外边距都在盒子外部，等盒子的宽高都算完了才加在四周 [3]。所以“让这个盒子连同左右各 10px 的间距一共占 250px”，是要你自己从 250px 里减掉 20px 的，`box-sizing` 帮不上。

另外，并非所有元素都默认用 `content-box`：浏览器给 `table`、`select`、`button`，以及 `type` 是 `submit`、`checkbox`、`color` 这类按钮和勾选形的 `input` 设的就是 `border-box`；普通文本框的 `input` 用的仍是 `content-box` [2]。同样是设 `width`，在按钮上和在自己的 `div` 上含义并不一样。

## 实际代码里通常怎么设

既然 `border-box` 更好用，常见做法是全局统一：

```css
html {
  box-sizing: border-box;
}
*,
*::before,
*::after {
  box-sizing: inherit;
}
```

这段写法有三处值得解释。

第一，必须把 `*` 也写进去，因为 `box-sizing` 本身**不继承** [4]。只给 `html` 设了 `border-box`，页面上的 `div` 还是 `content-box`。

第二，这里用的是 `inherit`（从父元素继承），而不是直接在 `*` 上写 `border-box`。这样如果哪个元素被单独改回 `content-box`，它内部的后代也会跟着变回去，不会出现一半一种模式的情况。这也是 MDN 教程推荐的写法 [3]。

第三，`*::before`、`*::after` 是伪元素，它们在页面上同样是盒子，同样会被 `box-sizing` 影响。现在照着抄下来就行，不用深究这两个词的写法。

## 遇到了尺寸不对，先在 DevTools 里看

这类问题不需要靠心算排查。在 Chrome 的开发者工具里打开 Elements 面板，把鼠标移到某个元素上，页面上会出现四色叠加层：内容区蓝色、内边距绿色、边框黄色、外边距橙色。选中元素后，右侧 Computed 面板里有一张盒模型图，直接把四个区域各自四个方向的数值都标出来。

尺寸和预期不符时，就在这张图上看是哪一层超出了你的估计，再回去改对应属性，比在脑子里做加减法可靠得多。

## 这套规则适用的范围

以上内容完整适用于块级盒子，比如 `div`、`p`、`h1`。行内元素只用到盒模型的一部分：给一段文字里的 `<a>` 设 `width`、`height` 通常不起作用，`padding` 和 `margin` 的表现也和块级元素不同。

原因不在盒模型本身，而在于“元素在页面上怎么排布”这套规则，也就是正常流（normal flow）和 `display`——那是下一篇要讲的事。

<details>
<summary>小练习（答案在折叠里）</summary>

一个 `div` 写了 `box-sizing: content-box; width: 300px; padding: 0 25px; border: 5px solid gray; margin: 10px;`。它在页面上横向占多少空间？

答案：内容区 300px，内边距 $2\times 25 = 50$px，边框 $2\times 5 = 10$px，盒子可见宽度是 360px；再加上左右外边距 $2\times 10 = 20$px，它把邻居推开、实际占用的横向空间一共是 380px。如果改成 `border-box`，可见宽度就是 300px，内容区被压到 $300-50-10=240$px，加上外边距一共占用 320px。

</details>

## 术语表

- 盒模型：把每个元素看成一圈套一圈的四个矩形区域——内容区、内边距、边框、外边距，它决定元素多大、把邻居推开多远。
- 内容区：文字和图片真正放置的地方，默认的 `width`、`height` 量的就是它。
- 内边距：内容与边框之间的空白，在盒子内部，背景会铺满它。
- 外边距：盒子外面的空白，不计入 `width`，永远在最后加。
- `box-sizing`：决定 `width`、`height` 量的是内容区（`content-box`）还是内容区加内边距加边框（`border-box`）。
- border box：内容区、内边距、边框三者合起来的外沿，`border-box` 模式下 `width` 量的就是这一层。

## 来源

1. [MDN: Introduction to the CSS box model — 四个区域各自的边界，以及哪个属性设定哪一层尺寸](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Box_model/Introduction)
2. [MDN: box-sizing — content-box 与 border-box 的官方计算示例，以及默认使用 border-box 的元素清单](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/box-sizing)
3. [MDN: The box model（教程）— 全局切换 border-box 的写法与 inherit 的用意，外边距在盒子尺寸之后添加](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Box_model)
4. [CSS Box Sizing Module Level 3 — box-sizing 的规范定义：内容区下限为 0、属性不可继承](https://drafts.csswg.org/css-sizing-3/)

---

原文：https://pangzhengboyin.com/articles/css-box-model-and-box-sizing-5f60a79f

> **庞征博引** · 想学的，慢慢都会
>
> 庞征博引是把想学的东西写成连载的 AI 学习工具。说出想学什么，它会先了解你的基础，再把主题写成一篇篇 5–10 分钟能读完的文章；边读边问，接下来学什么跟着你走。这篇就是这样写出来的。
>
> 开始你自己的连载 → https://pangzhengboyin.com
