# 元素默认是怎么排列的：块级独占一行、行内首尾相接，以及 inline-block 补上的那一块

外层类型决定盒子怎么和邻居排、内层类型决定它内部怎么排，以及 inline-block 的三个坑和 Flexbox 为什么接手

> CSS 基础 · 页面渲染 · 盒模型与尺寸计算 · 约 6 分钟 · 10 月 08 日

## 本篇要点

1. 默认排版先看每个盒子的外层类型，决定它和邻居怎么排；再看内层类型，决定它自己的孩子怎么排，`display` 一直在同时描述这两件事。
2. 块级盒子从新的一行开始、一个接一个往下排；设了 `width` 之后右侧的空隙下一个块级元素也用不上，它仍然排在下面。
3. 上下相邻的块级盒子外边距会合并，取两者中较大的那个，而不是相加；换成 `inline-block` 后不再合并。
4. 行内盒子像句子里的词一样首尾相接，排不下就折成新的行盒；水平方向的 margin、border、padding 生效，垂直方向的不生效。
5. 纯行内盒子的 `width`、`height` 和上下 `margin` 完全不生效，上下 `padding`、`border` 会画出来但不撑开行盒、会压住相邻行；行盒高度由 `line-height` 决定，行内盒子自身的高度由字体决定。
6. `<img>` 默认是行内，但作为替换元素它有固有尺寸，所以宽高照样生效；`input`、`button`、`select` 的默认值是 `inline-block`，不是 `inline`。
7. `inline-block` 等于 `inline flow-root`：外层留在文字流里，内层是完整的块级布局，所以宽高生效、四边间距生效，还会建立独立的格式化上下文。
8. `inline-block` 并排的三个坑来自同一原因：标签间空白会渲染成词间空格（约 `0.25em`，且随字体变化）；默认按基线对齐，图片下方因此多出一条缝；宽度分配和每行几个都得靠自己算。
9. `display: flex` 保留容器自己块级的外层行为，只把内层换成 flex 规则，直接子元素成为 flex item，于是间距、分配和对齐都交给容器统一处理。

---

上一篇解决了一个盒子自己有多大：`width` 量的是内容区还是边框外沿，`padding` 和 `border` 是加上去还是吃进去。但页面上的盒子不止一个，接下来要问的是它们**彼此之间怎么摆**。给三个 `div` 各写 `width: 200px`，它们不会并排，而是一个叠一个；把 `div` 换成 `span`，却又无法给 `span` 设宽高。

这两件事是同一套规则的两面：默认排版规则（normal flow，常规流）先看每个盒子的**外层类型**，决定它和邻居怎么排；再看**内层类型**，决定它自己的孩子怎么排 [1]。理解了这两个关键词，`div` 和 `span` 的差别、以及 `inline-block` 为什么存在，就都能自己推出来。

## 块级盒子：从新的一行开始，一个接一个往下排

在块级格式化上下文里，盒子从包含块顶部开始，一个接一个**垂直**排列 [1]。`<div>`、`<p>`、`<h1>`、`<ul>` 这些元素的默认 `display` 就是 `block`。

这里有两个容易混淆的点，值得分开说。

第一，块级盒子一定**另起一行**，后面的块级元素也一定再起一行。`width` 设成 200px 时，盒子本身就占 200px 宽，右侧确实空着，但那个空间**不会被下一个块级元素用掉**——它仍然排在下面 [1]。所谓“块级元素占满一行”，说的是它 `width: auto` 时会被拉满，以及它在行方向上是独占的，不是“无论写多宽都铺满”。

第二，上下相邻的两个块级盒子，外边距会**合并**（collapse）：上面那个有 `margin-bottom: 30px`，下面那个有 `margin-top: 20px`，两者之间的实际距离是 30px，取大的那个，而不是相加 [1]。这是块级格式化上下文里的规则，换成 `inline-block` 就不合并。

`width`、`height`、四个方向的 `margin`、`padding`、`border` 在块级盒子上全部生效——上一篇那套盒模型算法，主要就是为块级盒子准备的。

## 行内盒子：像句子里的词一样首尾相接

`<span>`、`<a>`、`<strong>` 这些元素的默认 `display` 是 `inline`。行内盒子不是从上往下排，而是**沿着文字行走的方向一个接一个**，就像句子里的词：排不下就折到下一行，这些折出来的行在规范里叫行盒（line box）[4]。

关键在于：**水平方向和垂直方向的规则不一样**。

水平方向，也就是文字往前走的方向，`margin`、`border`、`padding` 都正常生效，会把左右的文字推开 [4]。

垂直方向则几乎全部失效：

- `width` 和 `height` **完全不生效**——给 `span` 设宽高没有任何反应 [2]；
- 上下 `margin` 也**不生效**；
- 上下 `padding` 和 `border` 会**画出来**，但不撑开行盒，不会把上下两行文字推开，它们会直接压在相邻行的内容上 [4]；
- 行内盒子的高度取决于字体本身，不是你能用 `height` 去控制的量 [2]。

一行占多高由 `line-height` 决定；而某个行内盒子“自己多高”，是字体决定的事，两者不是一回事。同一行里的行内盒子按**基线**（baseline，字母底部那条对齐线）对齐，所以把一行里混着不同字号的文字时，行盒会被撑得比预期高。

有一个例外值得单独记住：`<img>` 默认 `display` 也是 `inline`，但它是**替换元素**（replaced element），内容来自 CSS 之外、自带固有尺寸，所以 `width`、`height` 在它身上照样生效，用起来像 `inline-block` [7]。同样地，`input`、`button`、`select` 的默认值是 `inline-block`，不是 `inline`。“行内不能设宽高”这句话，对这些元素不成立。

## inline-block：留在句子里的块

想放一排卡片，天然会想到：既想让它们并排，又想让每个都能设宽高、有四边内边距。`display: inline-block` 就是为这个场景准备的。

按规范的定义，`inline-block` 等于 `inline flow-root` [3]：外层是 `inline`，所以它**留在文字流里**，不会另起一行，像词一样跟着前面的内容排；内层是 `flow-root`，也就是它内部自己是一套完整的块级布局。于是——[2]

- `width`、`height` **生效**；
- 四个方向的 `margin`、`padding`、`border` 都生效，会把周围的元素推开，而不是像纯 `inline` 那样上下重叠；
- 它**建立了一个独立的格式化上下文**，所以它的外边距不会和外面合并，内部的浮动也不会漏出去 [5]。

一句话概括：`inline` 的排位，`block` 的内部。但它并排出来的效果，几乎总会让人踩到三个坑。

**第一个坑是缝隙。** HTML 里标签之间敲的回车和缩进会被当成一个空格，而在行内上下文里，一个空格就是宽度真实的词间空格。所以写成：

```html
<div class="card">A</div>
<div class="card">B</div>
```

这两个 `inline-block` 之间会多出一小段空白。常见的说法是约 `0.25em`，在 16px 字号下差不多 4px，但具体宽度取决于字体 [6]。这不是 bug，就是“空格之间的空格”被如实渲染了。常见绕法有三种：把标签贴在一起写或塞注释、给父元素设 `font-size: 0` 再在子元素上把字号设回来、用负 `margin` 顶回去 [6]。它们的共同点是都在**抵消**一个你没打算要的东西，而且负 `margin` 的具体数值还得看字体，不同字体算出来不一样 [6]。

**第二个坑是基线对齐。** `inline-block` 默认按基线对齐：盒子里有文字时，按最后一行文字的基线对齐；盒子里没有文字内容时，基线取盒子的底边。图片放在一行文字里时，图片按自己的底边对齐文字的基线，而基线和行底之间还要留给字母下伸的部分（比如 g、y 的下半截），图片下方于是多出一条缝。要清掉它，得靠 `vertical-align` 手动纠正。

**第三个坑是宽度和分行不受你控制。** 想让一排卡片等宽并铺满一整行，你得自己算百分比，还要把那点缝隙的宽度减掉；卡片多了换行时，每行放几个、剩余空间怎么分配，只能看它自然折行，没办法让它们均匀分布或者在行内居中。

这三个坑是同一个原因：**`inline-block` 的排布规则来自行内上下文，而那个上下文是为“排文字”设计的，不是为了排一排布局用的方块**。你想管的是这一排怎么分、间距多少、上下怎么对齐，它关心的却是这些方块像词一样怎么摆。

## 这就是 Flexbox 要接手的地方

`display: flex` 把这两件事拆开：容器自己的外层类型仍然是 `block`，所以它还是独占一行；但它建立一个 flex 格式化上下文，直接子元素不再走块级和行内那套规则，而是变成 flex item，由容器统一决定方向、间距和对齐 [1]。

换成 flex 之后，前面三个坑都不需要绕了：缝隙问题不存在了（flex item 之间没有空白文字参与），盒子之间要等距有 `gap`，水平方向怎么分配有 `justify-content`，垂直方向怎么对齐有 `align-items`，多行怎么排有 `flex-wrap`。以前用 `inline-block` 加手算百分比加缝隙 hack 拼出来的东西，变成容器上的一两条声明。

## 术语表

- normal flow（常规流）：页面在没有任何布局改动时使用的默认排版规则，块级盒子上下排，行内盒子沿着文字方向排。
- 外层类型与内层类型：`display` 同时描述的两件事，外层决定这个盒子怎么和邻居排，内层决定它里面的盒子按什么规则排。
- 块级格式化上下文：块级盒子所用的排版规则集合，特征是垂直堆叠、行方向独占、相邻上下外边距会合并。
- 行盒（line box）：行内盒子沿着文字方向排不下时折行所形成的矩形区域，一行的高度由 `line-height` 决定。
- 替换元素：内容来自 CSS 之外的元素，例如 `<img>`，自带固有尺寸，所以即便默认是行内也能设宽高。
- `inline-block`：等于 `inline flow-root`，排位上像行内元素、内部像块级容器，因此宽高与四边间距都生效。
- flex 格式化上下文：`display: flex` 让容器为直接子元素建立的新排版规则，子元素不再走块级和行内那套规则。

## 来源

1. [MDN：Block and inline layout in normal flow — 常规流中块级与行内布局规则、外边距合并](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Display/Block_and_inline_layout)
2. [MDN：The box model — 行内元素哪些属性不生效，以及 inline-block 的作用](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Box_model)
3. [MDN：display 属性参考 — inline-block 等价于 inline flow-root，外层与内层类型定义](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/display)
4. [MDN：Inline formatting context — 行盒，以及垂直方向 padding/border 不撑开行盒](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Inline_layout/Inline_formatting_context)
5. [MDN：Introduction to formatting contexts — 哪些元素建立独立格式化上下文，外边距不跨边界合并](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Display/Formatting_contexts)
6. [CSS-Tricks：Fighting the Space Between Inline-Block Elements — 缝隙来源与常见绕法](https://css-tricks.com/fighting-the-space-between-inline-block-elements/)
7. [MDN：img 元素参考 — 默认 display: inline，但按 inline-block 方式使用固有尺寸](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img)

---

原文：https://pangzhengboyin.com/articles/block-inline-inline-block-normal-flow-83ff970b

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