很多人写 CSS 布局靠的是”试”:加一个属性看看,不对再换一个。能跑就行,但换个场景又懵。问题不在于属性记得不够多,而在于缺一套心智模型——一旦你能回答”现在是谁在分配空间、往哪个方向分配”,绝大多数布局都会变得可推理。
一切的起点:文档流↗
浏览器默认的排版方式叫正常文档流(Normal Flow)。它的规则朴素得近乎天真:
- 块级元素(
div、p、h1…)从上到下依次堆叠,每个独占一行,默认撑满父容器的宽度。 - 行内元素(
span、a、em…)在一行内从左到右排列,排不下才换行。
文档流的可贵之处在于它是”自适应”的:你不写任何布局代码,内容也能合理地流动、换行、适配宽度。好的布局,往往是尽量顺着文档流,而不是对抗它。 只有当默认流满足不了需求时,我们才引入更强的布局系统。
在动手之前,先补两个决定性的概念:盒模型和包含块。
盒模型:每个元素都是一个盒子↗
CSS 里每个元素都是一个矩形盒子,由内到外四层:
┌─────────────── margin(外边距,盒子之间的距离)
│ ┌──────────── border(边框)
│ │ ┌───────── padding(内边距,内容与边框的距离)
│ │ │ ┌────── content(内容区)
这里有个历史包袱:默认的 box-sizing: content-box 下,你设的 width 只算内容区,padding 和 border 会额外撑大盒子——于是”我明明设了 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。很多需求——一段文字、一列堆叠的区块、一张会自动换行的图片——普通文档流本身就处理得很好,加了布局属性反而可能破坏它的自适应能力。
建立心智模型的意义,正是在于知道什么时候该出手、出哪只手:
- 默认信任文档流,它天生自适应。
- 一个方向上要分配空间 → Flexbox。
- 两个方向上要对齐到网格 → Grid。
- 遇到”百分比不对""宽度超了”,先回头查盒模型和包含块。
把这几条内化了,你会发现自己不再是”试属性”,而是在”推布局”。