CSS Grid 布局全面指南
很多同学第一次接触 Grid 时,会觉得它“语法很多、名字很像、看起来很强但不太敢用”。
这很正常,因为 CSS Grid 不是某一个单独属性,而是一整套二维布局系统。它把“页面怎么分行、怎么分列、元素放到哪一格、空白怎么分配、内容怎么对齐”这些问题统一交给浏览器处理。
如果你把 Flex 理解成“排一条线”,那 Grid 更像是“先画出棋盘,再把内容放进去”。
本文会把 CSS Grid 中最核心的属性、单位、函数、关键字、概念和实战写法一次讲完整,并尽量用通俗语言解释清楚。
为了方便你建立直觉,下面的关键代码示例后面都补了“渲染结果”预览,你可以边看代码边对照效果。
一、先记住一句话:Grid 是二维布局
Flexbox 很擅长处理一维布局:
- 要么主要控制一行
- 要么主要控制一列
- 重点在“主轴 + 交叉轴”上的分配
Grid 则擅长处理二维布局:
- 同时定义行和列
- 同时决定元素的横向和纵向位置
- 更适合页面骨架、卡片墙、仪表盘、后台布局、复杂响应式布局
| 布局方式 | 更擅长什么 | 典型场景 |
|---|---|---|
Flexbox | 一维排列 | 导航栏、按钮组、单行卡片、居中对齐 |
Grid | 二维布局 | 页面主结构、卡片矩阵、看板、图文混排 |
最简单的开启方式:
.container {
display: grid;
}
或者:
.badge-list {
display: inline-grid;
}
两者区别是:
display: grid:容器在外部表现为块级盒子display: inline-grid:容器在外部表现为行内级盒子
当元素设置为 display: grid 或 display: inline-grid 时,它会创建 Grid Formatting Context(GFC,网格格式化上下文)。你可以简单理解为:这个容器内部开始按照 Grid 规则排版,而不是普通文档流规则。
二、Grid 的核心概念
学 Grid,第一步不是背属性,而是先把这些概念分清。
| 概念 | 英文 | 你可以怎么理解 |
|---|---|---|
| 网格容器 | Grid Container | 开启了 display: grid 的父元素 |
| 网格子项 | Grid Item | 网格容器的直接子元素 |
| 网格线 | Grid Line | 每一列、每一行的边界线 |
| 轨道 | Grid Track | 两条相邻网格线之间的空间;一列或一行就是一个轨道 |
| 单元格 | Grid Cell | 一行和一列交叉形成的最小格子 |
| 区域 | Grid Area | 多个单元格组成的矩形区域 |
| 显式网格 | Explicit Grid | 你通过 grid-template-* 明确定义出来的网格 |
| 隐式网格 | Implicit Grid | 子项超出显式网格后,浏览器自动补出来的网格 |
2.1 行、列、线编号怎么理解?
看这个例子:
.layout {
display: grid;
grid-template-columns: 200px 1fr 1fr;
grid-template-rows: 80px auto;
}
它表示:
- 一共有 3 列轨道
- 一共有 2 行轨道
- 因为“轨道数 = 线之间的空间”,所以:
- 3 列会有 4 条列线
- 2 行会有 3 条行线
因此你可以这样放元素:
.sidebar {
grid-column: 1 / 2;
grid-row: 1 / 3;
}
.main {
grid-column: 2 / 4;
grid-row: 2 / 3;
}
其中:
1 / 2表示从第 1 条线到第 2 条线2 / 4表示从第 2 条线跨到第 4 条线-1表示最后一条线
所以这个写法很常见:
.full-width {
grid-column: 1 / -1;
}
它的意思是:从第一条线铺到最后一条线,也就是整行占满。
2.2 justify-* 和 align-* 别机械地背成“水平/垂直”
在大多数中文网站里,你可以暂时粗略理解为:
justify-*:更接近水平方向align-*:更接近垂直方向
但更准确的说法是:
justify-*控制 inline 轴align-*控制 block 轴
这也是为什么在不同书写模式下,它们不一定永远对应“左/右”和“上/下”。
三、从一个最小可用示例开始
先看一段最基础的 Grid 代码:
<div class="cards">
<article>卡片 1</article>
<article>卡片 2</article>
<article>卡片 3</article>
<article>卡片 4</article>
<article>卡片 5</article>
<article>卡片 6</article>
</div>
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
这段代码做了三件事:
display: grid:告诉浏览器,这个容器内部用 Grid 排版grid-template-columns: repeat(3, 1fr):定义 3 列,每列平分剩余空间gap: 16px:定义网格项之间的间距
效果可以理解成:
- 第一行:卡片 1、2、3
- 第二行:卡片 4、5、6
- 行高默认由内容撑开
渲染结果:
如果你把列数改成:
grid-template-columns: 240px 1fr 1fr;
就表示:
- 第一列固定 240px
- 后两列平分剩余空间
这就是 Grid 最重要的思维方式:先定义轨道,再让元素进入轨道。
四、容器属性:先把网格骨架搭出来
下面这张表,先把 Grid 容器常用属性总览一遍:
| 属性 | 作用 | 常见值 |
|---|---|---|
display | 开启 Grid | grid、inline-grid |
grid-template-columns | 定义列轨道 | 1fr 1fr、repeat(3, 1fr) |
grid-template-rows | 定义行轨道 | 80px auto 1fr |
grid-template-areas | 用命名区域描述布局 | "header header" |
grid-template | 上面三者的简写 | "header header" 64px / 220px 1fr |
column-gap | 列间距 | 16px |
row-gap | 行间距 | 24px |
gap | 行列间距简写 | 16px 24px |
grid-auto-columns | 隐式列尺寸 | 160px、minmax(0, 1fr) |
grid-auto-rows | 隐式行尺寸 | 120px、auto |
grid-auto-flow | 自动放置方向 | row、column、dense |
justify-items | 每个子项在单元格内的 inline 轴对齐 | start、center、stretch |
align-items | 每个子项在单元格内的 block 轴对齐 | start、end、stretch |
place-items | align-items + justify-items 简写 | center stretch |
justify-content | 整个网格轨道在容器中的 inline 轴分布 | center、space-between |
align-content | 整个网格轨道在容器中的 block 轴分布 | start、space-evenly |
place-content | align-content + justify-content 简写 | center space-between |
grid | 多个 Grid 长属性的总简写 | auto-flow dense / repeat(4, 1fr) |
4.1 display: grid 与 display: inline-grid
.page {
display: grid;
}
.tags {
display: inline-grid;
grid-auto-flow: column;
gap: 8px;
}
grid:外部像块元素,会独占一行inline-grid:外部像行内元素,可以和文字、其他行内内容并排
4.2 grid-template-columns
它用来定义每一列多宽。
.layout {
display: grid;
grid-template-columns: 220px 1fr 1fr;
}
上面表示:
- 第一列固定
220px - 后两列各占
1fr
你还能混合更多写法:
grid-template-columns: 12rem minmax(0, 1fr) fit-content(20rem);
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(2, minmax(180px, 1fr));
grid-template-columns: [sidebar-start] 240px [sidebar-end content-start] 1fr [content-end];
grid-template-columns: subgrid;
这里出现了很多 Grid 中最常见的单位和函数:
fr:剩余空间分配单位repeat():重复轨道定义minmax():设置最小值和最大值范围fit-content():在不超过上限的前提下尽量贴合内容[]:给网格线命名subgrid:继承父网格的轨道
4.3 grid-template-rows
它用来定义每一行多高。
.page {
display: grid;
min-height: 100vh;
grid-template-rows: 64px 1fr auto;
}
表示:
- 第一行固定 64px(顶部导航)
- 第二行吃掉剩余空间(主内容区)
- 第三行由内容高度决定(底部)
如果你在 grid-template-rows 里写百分比,例如 50% 50%,那容器本身通常需要有一个明确高度,否则结果往往不如预期。
4.4 grid-template-areas
这是 Grid 最适合初学者、也最适合读代码的写法之一。
<div class="page">
<header class="header">头部</header>
<aside class="sidebar">侧栏</aside>
<main class="main">主内容</main>
<footer class="footer">底部</footer>
</div>
.page {
display: grid;
grid-template-columns: 220px minmax(0, 1fr);
grid-template-rows: 64px 1fr auto;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
min-height: 100vh;
gap: 16px;
}
.header {
grid-area: header;
}
.sidebar {
grid-area: sidebar;
}
.main {
grid-area: main;
}
.footer {
grid-area: footer;
}
渲染结果:
头部
侧栏
主内容
底部
这段代码最大的优点是:布局一眼就能看出来。
使用 grid-template-areas 时要记住几条规则:
- 每一行字符串里的列数必须一致
.表示空白单元格- 同名区域必须组成矩形,不能拐弯
- 给元素赋值时使用
grid-area: 名称
比如:
grid-template-areas:
"cover title"
"cover meta";
当你声明了 grid-template-areas 后,浏览器会自动生成对应的命名线,例如 header-start、header-end。这让你后面可以在 grid-column、grid-row 中继续引用这些名字。
4.5 grid-template
这是下面几个属性的简写:
grid-template-rowsgrid-template-columnsgrid-template-areas
例如:
.page {
display: grid;
grid-template:
"header header" 64px
"sidebar main" 1fr
"footer footer" auto
/ 220px minmax(0, 1fr);
}
它等价于分别设置:
- 三行:
64px、1fr、auto - 两列:
220px、minmax(0, 1fr) - 区域:
header / sidebar / main / footer
如果你觉得这个简写太难读,完全可以优先使用长属性。实战中,可读性往往比“写得短”更重要。
4.6 gap、row-gap、column-gap
它们用于定义网格项之间的间距。
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px 24px;
}
这相当于:
row-gap: 16px;
column-gap: 24px;
你还会在旧代码里看到:
grid-gapgrid-row-gapgrid-column-gap
现在更推荐统一使用:
gaprow-gapcolumn-gap
4.7 grid-auto-rows 与 grid-auto-columns
当子项超出了你定义的显式网格,浏览器会自动创建隐式轨道。
这时候,这两个属性就负责定义这些“自动补出来的轨道”的尺寸。
.gallery {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 120px;
}
如果某个元素被放到了第 5 行,而你只显式定义了前 2 行,那么第 3、4、5 行就是隐式行,它们会使用 grid-auto-rows: 120px。
同理:
grid-auto-columns: minmax(160px, 1fr);
表示隐式列的宽度规则。
4.8 grid-auto-flow
它决定没有显式指定位置的元素如何自动排入网格。
.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-flow: row;
}
常见值:
| 值 | 含义 |
|---|---|
row | 默认值,按行依次填充 |
column | 按列依次填充 |
dense | 尽量回填空洞 |
row dense | 按行填充,并尝试回填前面的空位 |
column dense | 按列填充,并尝试回填前面的空位 |
例如:
.masonry-like {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 80px;
grid-auto-flow: row dense;
gap: 12px;
}
dense 常用于“图片墙补洞”这类场景,但它可能让视觉顺序和 DOM 顺序不完全一致,因此要谨慎使用在有明显阅读顺序的内容里。
4.9 justify-items、align-items、place-items
这组属性控制的是:每个子项在自己的单元格内部怎么对齐。
.board {
display: grid;
grid-template-columns: repeat(3, 160px);
grid-auto-rows: 120px;
justify-items: center;
align-items: center;
}
常见值:
startendcenterstretch(默认值)
其中:
justify-items:控制 inline 轴对齐align-items:控制 block 轴对齐place-items:前者 + 后者的简写
例如:
place-items: center stretch;
相当于:
align-items: center;
justify-items: stretch;
渲染结果:
4.10 justify-content、align-content、place-content
这组属性经常和 justify-items / align-items 混淆。
它们控制的不是“单个子项”,而是:整个网格轨道作为一个整体,在容器里怎么分布。
.wrapper {
display: grid;
width: 900px;
height: 500px;
grid-template-columns: repeat(3, 160px);
grid-template-rows: repeat(2, 120px);
justify-content: center;
align-content: space-between;
}
上面之所以能看出效果,是因为:
- 容器宽
900px - 轨道总宽不到
900px - 容器高
500px - 轨道总高不到
500px
这时才存在“多余空间如何分配”的问题。
常见值:
startendcenterstretchspace-betweenspace-aroundspace-evenly
place-content 是:
place-content: center space-between;
相当于:
align-content: center;
justify-content: space-between;
4.11 grid 总简写
grid 是一个很强但也很容易写晕的简写属性。
例如:
.gallery {
display: grid;
grid: auto-flow dense 120px / repeat(4, 1fr);
}
它可以同时设置:
grid-auto-flowgrid-auto-rowsgrid-template-columns
如果你在团队开发里发现大家不容易看懂,建议优先拆开写。
五、子项属性:把元素放到正确的位置
Grid 子项属性的核心目标只有一个:决定元素占哪几列、哪几行,以及在格子里怎么对齐。
| 属性 | 作用 | 典型写法 |
|---|---|---|
grid-column-start | 指定列起始线 | 1、sidebar-start、span 2 |
grid-column-end | 指定列结束线 | 3、-1、span 3 |
grid-row-start | 指定行起始线 | 1、header-start |
grid-row-end | 指定行结束线 | 4、span 2 |
grid-column | 列起止简写 | 1 / 3、2 / span 2 |
grid-row | 行起止简写 | 1 / 4、auto / span 2 |
grid-area | 区域名称或四值简写 | main、1 / 2 / 3 / 4 |
justify-self | 当前子项在单元格内的 inline 轴对齐 | start、center |
align-self | 当前子项在单元格内的 block 轴对齐 | end、stretch |
place-self | align-self + justify-self 简写 | center stretch |
order | 调整视觉顺序 | 2、-1 |
z-index | 控制重叠层级 | 1、10 |
5.1 grid-column-start / grid-column-end
.hero {
grid-column-start: 1;
grid-column-end: 3;
}
等价于:
.hero {
grid-column: 1 / 3;
}
你可以写的值包括:
- 数字线号:
1、2、-1 - 命名线:
content-start - 跨度:
span 2 auto
例如:
.banner {
grid-column: 1 / -1;
}
.card-large {
grid-column: 2 / span 2;
}
.aside {
grid-column: sidebar-start / sidebar-end;
}
5.2 grid-row-start / grid-row-end
它们的规则和列完全一样,只不过变成纵向。
.promo {
grid-row-start: 1;
grid-row-end: 3;
}
等价于:
.promo {
grid-row: 1 / 3;
}
再看一个常见写法:
.card-tall {
grid-row: auto / span 2;
}
意思是:起始行由自动放置算法决定,但纵向跨两行。
5.3 grid-column 与 grid-row
这是最常用的简写形式。
.item-a {
grid-column: 1 / 3;
grid-row: 1 / 2;
}
.item-b {
grid-column: 3 / 4;
grid-row: 1 / 3;
}
你也可以和命名线混合:
.content {
grid-column: content-start / content-end;
}
渲染结果:
B
C
D
E
5.4 grid-area
grid-area 有两种非常常见的用法。
第一种:给命名区域赋值
.main {
grid-area: main;
}
它通常配合 grid-template-areas 使用。
第二种:一次写完四条线
语法是:
grid-area: row-start / column-start / row-end / column-end;
例如:
.feature {
grid-area: 1 / 2 / 3 / 4;
}
意思是:
- 从第 1 条行线开始
- 从第 2 条列线开始
- 到第 3 条行线结束
- 到第 4 条列线结束
5.5 justify-self、align-self、place-self
如果容器上统一设置了:
.board {
display: grid;
justify-items: stretch;
align-items: stretch;
}
某个子项想“单独例外”,就可以用 *-self 覆盖:
.avatar {
justify-self: center;
align-self: start;
}
或者:
.avatar {
place-self: start center;
}
相当于:
align-self: start;
justify-self: center;
5.6 order
order 不是 Grid 专属属性,但它也适用于 Grid 子项。
.important {
order: -1;
}
作用是改变视觉排列顺序。
但一定要注意:
order改变的是视觉顺序- DOM 顺序、阅读顺序、Tab 顺序不一定同步改变
所以:
- 做少量视觉微调可以
- 不要用它彻底打乱信息语义顺序
5.7 z-index 与重叠
Grid 子项是可以重叠的,只要它们被放到了同一片区域,或者跨行跨列发生覆盖。
.chart {
grid-column: 1 / 4;
grid-row: 1 / 3;
}
.floating-panel {
grid-column: 3 / 5;
grid-row: 1 / 2;
z-index: 2;
}
这里有一个容易被忽视的细节:
Grid 子项可以直接使用
z-index控制层级,即使没有显式写position。
这点和普通块元素不完全一样。
六、Grid 中最重要的单位、函数与关键字
很多同学真正卡住 Grid 的地方,不是属性名,而是这些值:fr、repeat()、minmax()、auto-fit……
下面逐个讲透。
6.1 fr:剩余空间分配单位
grid-template-columns: 1fr 2fr 1fr;
表示把剩余空间分成 4 份,三列分别拿 1、2、1 份。
注意,不是把容器总宽直接硬切成 1:2:1,而是先扣除:
- 固定宽轨道
gap- 某些最小内容约束
剩下的部分才交给 fr 分配。
例如:
grid-template-columns: 240px 1fr 1fr;
表示:
- 先拿出 240px 给第一列
- 再减去列间距
- 剩下空间后两列平分
渲染结果:
minmax(0, 1fr)在一些实际布局里,单纯写 1fr 可能因为子项的最小内容尺寸过大而撑爆容器。为了更稳,很多时候会写成:
grid-template-columns: 240px minmax(0, 1fr);
这相当于告诉浏览器:这一列最窄可以压到 0,最大再去吃剩余空间。
6.2 repeat():重复定义轨道
grid-template-columns: repeat(4, 1fr);
等价于:
grid-template-columns: 1fr 1fr 1fr 1fr;
它的好处是简洁、可读,并且可以和别的函数组合:
grid-template-columns: repeat(3, minmax(180px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
6.3 minmax():给轨道一个区间
grid-template-columns: minmax(200px, 1fr) 2fr;
意思是第一列:
- 最小 200px
- 最大可以长到
1fr
它特别适合这几类场景:
- 响应式卡片列
- 侧栏最小宽度保护
- 防止列在窄屏下压得过小
最常见组合:
repeat(auto-fit, minmax(240px, 1fr))
6.4 fit-content():内容导向,但有上限
grid-template-columns: 220px fit-content(320px) 1fr;
你可以把它理解成:
- 这一列尽量根据内容大小决定
- 但不要超过我给的上限
它很适合:
- 标签列
- 时间轴时间列
- 内容长度差异较大的描述区
6.5 min-content 与 max-content
这两个叫内在尺寸关键字(intrinsic sizing keywords)。
grid-template-columns: min-content 1fr max-content;
简单理解:
min-content:在不溢出的前提下,尽可能窄max-content:内容完全展开时所需的理想宽度
它们在“内容驱动布局”里很有用,但如果你还不熟,可以先重点掌握 fr、repeat()、minmax()。
6.6 auto
auto 的含义经常要结合上下文理解。
例如:
grid-template-columns: 200px auto 1fr;
这里中间列表示:尺寸由内容和可用空间共同参与决定。
在放置属性中:
grid-row: auto / span 2;
表示起始位置交给自动放置算法决定。
6.7 auto-fill 与 auto-fit
它们常和 repeat() + minmax() 一起出现:
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
二者都能让列数随容器宽度自动变化,但细节不同:
auto-fill:尽量“预留轨道”,哪怕有些轨道最后是空的auto-fit:会把空轨道折叠掉,让已有项目把空间吃满
如果你只是想做常见的“自适应卡片列表”,多数情况下优先从:
repeat(auto-fit, minmax(240px, 1fr))
开始就够了。
6.8 span
它表示“跨几个轨道”。
.card-large {
grid-column: 2 / span 2;
grid-row: auto / span 2;
}
含义是:
- 从第 2 条列线开始,横向跨 2 列
- 行起点自动决定,纵向跨 2 行
6.9 命名线 []
除了用数字线号,你还可以给网格线起名字。
.layout {
display: grid;
grid-template-columns:
[sidebar-start] 220px
[sidebar-end content-start] minmax(0, 1fr)
[content-end];
}
.main {
grid-column: content-start / content-end;
}
它的优点是:当布局变复杂后,语义名字往往比单纯写 1 / 3 更容易维护。
6.10 subgrid
subgrid 是 Grid 中非常值得了解的进阶能力。
它允许嵌套网格继承父网格的轨道,从而让多个子组件在列线上严格对齐。
.page {
display: grid;
grid-template-columns: 160px 1fr;
gap: 12px 24px;
}
.card-list {
grid-column: 1 / -1;
display: grid;
grid-template-columns: subgrid;
}
当你需要:
- 卡片标题列对齐
- 多块信息区共享列宽
- 嵌套组件仍然和页面主网格对齐
subgrid 就很有价值。
6.11 calc()、min()、max()、clamp()
Grid 轨道值也可以结合通用 CSS 数学函数使用。
grid-template-columns: 220px minmax(0, calc(100% - 220px));
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
grid-auto-rows: clamp(80px, 12vw, 160px);
它们的价值在于:
calc():混合不同单位计算min():取较小值max():取较大值clamp():设置最小值、理想值、最大值
七、自动放置算法:浏览器到底怎么“找空位”
如果你不给所有子项都手动写 grid-column / grid-row,浏览器会启动自动放置算法(auto-placement algorithm)。
你可以把它理解成:浏览器拿着一个游标,按规则依次给元素找坑位。
7.1 默认规则
默认情况下:
grid-auto-flow: row;
意思是浏览器优先沿着“行方向”把元素依次往后填。
7.2 dense 为什么能“补洞”?
看这个场景:
- 某个元素跨了 2 列
- 导致前面留下一个 1 列的小空位
- 后面的普通小卡片本来排不到那个位置
如果你开启:
grid-auto-flow: row dense;
浏览器会尝试回头把能塞进去的小元素补进这个洞里。
优点:
- 空间利用率更高
- 图片墙更紧凑
风险:
- 视觉顺序可能和 DOM 顺序不同
- 不适合文章流、表单流、严格阅读顺序内容
八、响应式 Grid 实战模式
8.1 自适应卡片墙:最常用的一种写法
.card-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 16px;
}
这句可以直接背下来,它几乎是 Grid 响应式布局里最常见的黄金写法。
它的含义是:
- 每张卡片最小 240px
- 放得下几列就放几列
- 剩余空间自动均分
非常适合:
- 文章卡片列表
- 商品列表
- 演示案例网格
渲染结果:
8.2 页面骨架布局
.dashboard {
display: grid;
min-height: 100vh;
grid-template:
"header header" 64px
"sidebar main" 1fr
/ 240px minmax(0, 1fr);
}
.dashboard__header {
grid-area: header;
}
.dashboard__sidebar {
grid-area: sidebar;
}
.dashboard__main {
grid-area: main;
}
@media (max-width: 960px) {
.dashboard {
grid-template:
"header" 64px
"main" 1fr
/ minmax(0, 1fr);
}
.dashboard__sidebar {
display: none;
}
}
这种写法的优势是:
- 结构清晰
- 响应式改造直观
- 读 CSS 就能看出布局变化
8.3 12 列栅格系统
很多设计稿喜欢用 12 列网格,你也可以直接用 Grid 实现:
.row {
display: grid;
grid-template-columns: repeat(12, minmax(0, 1fr));
gap: 16px;
}
.col-3 {
grid-column: span 3;
}
.col-4 {
grid-column: span 4;
}
.col-6 {
grid-column: span 6;
}
.col-12 {
grid-column: 1 / -1;
}
这比传统浮动栅格更自然,也更容易做响应式覆盖。
渲染结果:
span 6
span 3
全宽 span 12
8.4 嵌套布局与 subgrid
.article-list {
display: grid;
grid-template-columns: 120px 1fr;
gap: 12px 20px;
}
.article-card {
grid-column: 1 / -1;
display: grid;
grid-template-columns: subgrid;
align-items: start;
}
这样每张卡片内部的“缩略图列”和“正文列”就能严格继承父级列宽,不会一张卡片一个样。
九、Grid 和 Flex 到底怎么选?
这也是面试和实战里都非常高频的问题。
| 问题 | 更推荐谁 | 原因 |
|---|---|---|
| 一行按钮左右排开 | Flexbox | 一维分布更直接 |
| 页面主结构有行有列 | Grid | 二维定义更清晰 |
| 卡片自适应换列 | Grid | repeat(auto-fit, minmax()) 很强 |
| 单个卡片内部图标和文字对齐 | Flexbox | 组件内的一维对齐更轻量 |
| 后台仪表盘、多区域面板 | Grid | 更适合区域化布局 |
一个非常实用的经验是:
页面大骨架用 Grid,组件内部小排列用 Flex。
例如:
- 整个页面:
Grid - 卡片内部标题 + 按钮:
Flex
这两者不是对立关系,而是经常一起使用。
十、常见坑与排错清单
10.1 为什么写了 justify-content 没效果?
因为它控制的是整个网格轨道在容器中的分布。
如果你的轨道本来就已经把容器撑满了,就没有“多余空间”可以分配,自然看不出效果。
这时你可能真正想要的是:
justify-itemsalign-itemsjustify-selfalign-self
10.2 为什么 1fr 有时会把内容撑爆?
很多同学以为 1fr 就一定会乖乖收缩,其实不一定。
如果子项里有:
- 超长单词
- 不换行文本
- 超宽图片
white-space: nowrap
那轨道的最小内容尺寸可能会阻止它继续收缩。
常见解法:
grid-template-columns: 240px minmax(0, 1fr);
.content {
min-width: 0;
}
10.3 为什么 grid-template-areas 报错或不生效?
检查这几项:
- 每行列数是否一致
- 同名区域是否是矩形
- 元素上的
grid-area名称是否拼写一致 - 是否把
.写成了别的字符
10.4 auto-fit 和 auto-fill 总是分不清怎么办?
可以先记一个够用版本:
- 想让现有卡片尽量把空间铺满:
auto-fit - 想保留“潜在轨道”的占位概念:
auto-fill
日常卡片列表,大多数时候先用 auto-fit。
10.5 为什么图片墙用了 dense 后顺序怪怪的?
因为 dense 会为了补洞而尝试回填前面的空位。
所以:
- 做纯视觉瀑布感布局可以考虑
- 做新闻流、步骤流、表单流要谨慎
10.6 为什么明明定义了两行,后面元素还在往下长?
因为浏览器帮你创建了隐式网格。
这时要检查:
- 是否有元素被放到了更靠后的行列
- 是否设置了
grid-auto-rows/grid-auto-columns
10.7 为什么百分比行高看起来不对?
因为百分比高度往往依赖父容器有确定高度。没有明确高度时,百分比行高很容易“不按你脑补的方式工作”。
10.8 为什么视觉上能换顺序,但无障碍仍然有问题?
因为 Grid 改变的是视觉布局,而不是 DOM 本身的语义顺序。
所以:
- 阅读顺序敏感的内容,优先保证 DOM 顺序正确
- 不要只靠 Grid 把正文“视觉上挪来挪去”
10.9 实战排错时看什么工具?
浏览器 DevTools 的 Grid Overlay 非常有用。排查时重点看:
- 网格线编号
- 显式轨道和隐式轨道
- 区域名称
gap是否如预期- 子项到底跨了几行几列
十一、面试高频问答
Q1:Grid 和 Flex 的核心区别是什么?
参考答案:
Flex 更擅长一维布局,主要解决一行或一列里的分配与对齐;Grid 更擅长二维布局,可以同时定义行和列,适合页面骨架、卡片矩阵、后台布局等复杂场景。实战里常见做法是:大布局用 Grid,小组件内部用 Flex。
Q2:1fr 到底是什么意思?
参考答案:
fr 是剩余空间分配单位。浏览器会先扣除固定尺寸轨道、间距以及部分最小内容约束,再把剩余空间按 fr 比例分给对应轨道。所以 1fr 2fr 1fr 不是简单地把总宽度切成 1:2:1,而是把剩余空间切成 1:2:1。
Q3:auto-fit 和 auto-fill 的区别是什么?
参考答案:
两者都常用于 repeat(auto-xxx, minmax(...))。auto-fill 更倾向于保留可放置的轨道,即使有些轨道最后为空;auto-fit 会把空轨道折叠,让已有项目尽量铺满空间。做常规响应式卡片列表时,通常优先使用 auto-fit。
Q4:为什么很多人写 minmax(0, 1fr),而不是只写 1fr?
参考答案:
因为 Grid 子项可能受最小内容尺寸影响,导致单纯的 1fr 轨道无法继续收缩,最终把容器撑爆。minmax(0, 1fr) 明确告诉浏览器:这条轨道最小可以压到 0,最大再按剩余空间分配,从而更稳地处理超长内容。
Q5:显式网格和隐式网格有什么区别?
参考答案:
显式网格是开发者通过 grid-template-columns、grid-template-rows、grid-template-areas 明确定义出来的网格。隐式网格则是当子项超出显式范围时,浏览器自动创建的额外行列。隐式网格的尺寸由 grid-auto-rows 和 grid-auto-columns 控制。
Q6:grid-template-areas 有什么限制?
参考答案:
它要求每一行列数一致,同名区域必须组成矩形,不能出现 L 形、T 形等不规则区域。优点是语义化强、可读性高,非常适合页面结构布局。
Q7:justify-items / align-items 和 justify-content / align-content 的区别是什么?
参考答案:
justify-items 和 align-items 控制的是每个子项在自己单元格内部如何对齐;justify-content 和 align-content 控制的是整个网格轨道作为整体在容器中如何分布。前者管“格子里的内容”,后者管“整张网格在容器里的位置”。
Q8:什么时候适合使用 grid-auto-flow: dense?
参考答案:
当你更关注视觉紧凑度,而不是严格的视觉顺序时,可以考虑 dense,例如图片墙、卡片拼贴布局。它会尝试回填前面的空洞。但如果页面内容有明确阅读顺序,就要谨慎,因为 dense 可能导致视觉顺序和 DOM 顺序不一致。
Q9:grid-column: 1 / -1 为什么这么常见?
参考答案:
因为 -1 代表最后一条网格线,所以 grid-column: 1 / -1 的意思是从第一条列线铺到最后一条列线,也就是横向占满整行。这在横幅、分隔区、全宽模块中非常常见。
Q10:subgrid 解决了什么问题?
参考答案:
subgrid 解决的是嵌套布局里“子组件和父组件列线对不齐”的问题。它让子网格可以直接继承父网格的轨道定义,从而让多层组件共享同一套列宽和对齐规则,特别适合复杂列表、表格式卡片、信息面板等场景。
十二、总结
如果你刚学完这篇文章,至少应该记住这 6 句话:
- Grid 是二维布局,先定行列,再放元素。
- 容器属性负责画棋盘,子项属性负责落子。
fr、repeat()、minmax()是 Grid 最核心的尺寸组合。- 响应式卡片墙优先记住
repeat(auto-fit, minmax(240px, 1fr))。 justify-*/align-*要分清是管“子项”还是管“整张网格”。- 复杂页面骨架优先考虑 Grid,组件内部细节排版继续交给 Flex。
如果你愿意继续深入,下一个非常值得配套学习的主题是:
FlexboxBFC / IFC / GFC- CSS 单位与
min()/max()/clamp() - 响应式设计与媒体查询
把这些知识串起来,你对 CSS 布局的整体理解会完整很多。