CSS 布局的心智模型:文档流、Flexbox 与 Grid

与其记忆几十个属性,不如建立一套心智模型——搞清楚"谁负责在哪个方向上分配空间",布局就不再靠猜。

很多人写 CSS 布局靠的是”试”:加一个属性看看,不对再换一个。能跑就行,但换个场景又懵。问题不在于属性记得不够多,而在于缺一套心智模型——一旦你能回答”现在是谁在分配空间、往哪个方向分配”,绝大多数布局都会变得可推理。

一切的起点:文档流

浏览器默认的排版方式叫正常文档流(Normal Flow)。它的规则朴素得近乎天真:

  • 块级元素divph1…)从上到下依次堆叠,每个独占一行,默认撑满父容器的宽度。
  • 行内元素spanaem…)在一行内从左到右排列,排不下才换行。

文档流的可贵之处在于它是”自适应”的:你不写任何布局代码,内容也能合理地流动、换行、适配宽度。好的布局,往往是尽量顺着文档流,而不是对抗它。 只有当默认流满足不了需求时,我们才引入更强的布局系统。

在动手之前,先补两个决定性的概念:盒模型和包含块。

盒模型:每个元素都是一个盒子

CSS 里每个元素都是一个矩形盒子,由内到外四层:

┌─────────────── margin(外边距,盒子之间的距离)
│  ┌──────────── border(边框)
│  │  ┌───────── padding(内边距,内容与边框的距离)
│  │  │  ┌────── content(内容区)

这里有个历史包袱:默认的 box-sizing: content-box 下,你设的 width 只算内容区,paddingborder 会额外撑大盒子——于是”我明明设了 300px 怎么超出去了”。现代项目几乎都会全局改成更符合直觉的 border-box

*, *::before, *::after {
  box-sizing: border-box; /* width 包含 padding 和 border */
}

包含块:百分比到底相对谁

当你写 width: 50%,这个 50% 是相对谁的?答案是包含块(Containing Block)。简化理解:

  • 普通元素的包含块,是最近的块级祖先的内容区。
  • position: absolute 元素的包含块,是最近的已定位祖先position 不为 static 的祖先)。
  • position: fixed 元素的包含块,通常是视口

“我设了百分比却不生效/不是我想的那样”,十有八九是包含块判断错了。搞清楚”我在相对谁计算”,是布局推理的基本功。

Flexbox:一维方向上分配空间

当文档流不够用——比如要让一排元素水平居中、两端对齐、等分剩余空间——就轮到 Flexbox。它的心智模型是一维的:你先选定一个主轴方向,然后所有讨论都围绕”沿着主轴怎么分配空间”展开。

.container {
  display: flex;
  flex-direction: row;      /* 主轴:水平(默认)。改成 column 则主轴变垂直 */
  justify-content: center;  /* 主轴方向上如何对齐/分配 */
  align-items: center;      /* 交叉轴方向上如何对齐 */
  gap: 16px;                /* 子项之间的间距 */
}

记住一组对应关系,Flex 就不再靠猜:

你想控制的用哪个属性作用在
一排东西怎么排布、居中、两端对齐justify-content主轴
一排东西在另一个方向怎么对齐align-items交叉轴
单个子项如何伸缩抢空间flex: grow shrink basis主轴

其中 flex 是重点,它是三个值的缩写。最常用的两个写法:

.item { flex: 1; }      /* = 1 1 0:等分剩余空间,是"平均分列"的常用写法 */
.item { flex: 0 0 auto; } /* 不伸不缩,保持自身内容宽度 */

Flexbox 的适用场景一句话概括:内容驱动的、一个方向上的排布——导航栏、工具条、卡片内部的图文对齐。

Grid:二维网格里定位

Flexbox 管一个方向,而 Grid 同时管行和列两个方向。它的心智模型是:先在容器上画好一张网格,再把子项放进格子里。

.container {
  display: grid;
  grid-template-columns: 200px 1fr 1fr; /* 三列:定宽 + 两个等分 */
  grid-template-rows: auto 1fr auto;    /* 三行 */
  gap: 16px;
}

这里的 fr 是 Grid 引入的新单位,表示”剩余空间的一份”。1fr 1fr 就是把剩余空间平均切两份。配合 repeat()minmax(),可以用一行写出经典的响应式卡片墙——不用任何媒体查询:

.gallery {
  display: grid;
  /* 每列至少 220px,放得下就多放几列,放不下就自动换行 */
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 16px;
}

该用 Flex 还是 Grid?

这是最常被纠结的问题。给一个可操作的判据:

场景更适合
一排/一列元素,内容长度不定,要对齐或分配空间Flexbox
需要同时约束行和列,元素要对齐到网格线Grid
整页的宏观骨架(页头 / 侧栏 / 主区 / 页脚)Grid
组件内部的图文、按钮组、导航条Flexbox

一个好用的经验法则:由内容决定尺寸,用 Flex;由容器规划结构,用 Grid。 而且它们不是二选一——常见做法是外层用 Grid 搭页面骨架,每个区块内部再用 Flex 做局部对齐。

别忘了:优先顺着文档流

最后回到开头那句话。Flexbox 和 Grid 很强,但不意味着每个 div 都要 display: flex。很多需求——一段文字、一列堆叠的区块、一张会自动换行的图片——普通文档流本身就处理得很好,加了布局属性反而可能破坏它的自适应能力。

建立心智模型的意义,正是在于知道什么时候该出手、出哪只手

  1. 默认信任文档流,它天生自适应。
  2. 一个方向上要分配空间 → Flexbox。
  3. 两个方向上要对齐到网格 → Grid。
  4. 遇到”百分比不对""宽度超了”,先回头查盒模型和包含块。

把这几条内化了,你会发现自己不再是”试属性”,而是在”推布局”。


More like this