主题
Grid 布局
Grid 布局模型概述
CSS Grid Layout 是一个二维布局系统,可以同时控制行和列。与 Flexbox 的一维布局(一次只处理一个方向)不同,Grid 能让你先定义一个网格结构,再将元素放置到网格中的任意位置。
Grid 的设计哲学是布局驱动——先定义网格框架,再让内容填入其中。这与 Flex 的内容驱动(内容决定布局)形成互补。两者不是替代关系,而是针对不同场景的最佳选择。
核心术语
列轨道 (Column Track)
↓ ↓ ↓
col1 col2 col3
↓ ↓ ↓
┌─────────┬─────────┬─────────┐ ← 网格线 (Grid Line) 1
│ │ │ │
│ Cell │ Cell │ Cell │ ← 行轨道 (Row Track) 1
│ (1,1) │ (1,2) │ (1,3) │
├─────────┼─────────┼─────────┤ ← 网格线 2
│ │ │ │
│ Cell │ Cell │ Cell │ ← 行轨道 2
│ (2,1) │ (2,2) │ (2,3) │
├─────────┼─────────┼─────────┤ ← 网格线 3
│ │ │ │
│ Cell │ Cell │ Cell │ ← 行轨道 3
│ (3,1) │ (3,2) │ (3,3) │
└─────────┴─────────┴─────────┘ ← 网格线 4
↑ ↑
网格线 1 网格线 4(列方向)| 术语 | 说明 |
|---|---|
| Grid Container | 设置了 display: grid 的元素 |
| Grid Item | Grid Container 的直接子元素 |
| Grid Line | 网格线,构成网格结构的分界线,编号从 1 开始 |
| Grid Track | 两条相邻网格线之间的空间(即一行或一列) |
| Grid Cell | 四条网格线围成的最小单元格 |
| Grid Area | 一个或多个 Grid Cell 组成的矩形区域 |
网格线编号规则:从 1 开始计数。一个 3 列的网格有 4 条列网格线(1-4),一个 3 行的网格有 4 条行网格线(1-4)。也可以用负数从末尾计数,-1 表示最后一条线。
容器属性
display: grid / inline-grid
css
.container {
display: grid; /* 块级 Grid 容器 */
/* display: inline-grid; */ /* 行内 Grid 容器 */
}grid-template-columns / grid-template-rows
定义网格的列轨道和行轨道大小。这是 Grid 布局最核心的属性。
css
.container {
grid-template-columns: 200px 1fr 200px; /* 三列:固定-自适应-固定 */
grid-template-rows: 80px 1fr 60px; /* 三行:固定-自适应-固定 */
}尺寸单位
css
.container {
grid-template-columns:
200px /* 固定像素 */
1fr /* 弹性比例单位 */
30% /* 百分比 */
auto /* 由内容决定 */
min-content /* 内容最小宽度 */
max-content /* 内容最大宽度(不换行) */
minmax(200px, 1fr) /* 最小 200px,最大 1fr */
fit-content(300px) /* 由内容决定,但不超过 300px */
;
}fr 单位详解
fr(fraction)是 Grid 专属的弹性单位,表示剩余空间的等分份额。
css
.container {
width: 900px;
grid-template-columns: 1fr 2fr 1fr;
}总可用空间 = 900px
fr 总和 = 1 + 2 + 1 = 4
第 1 列 = 900 × 1/4 = 225px
第 2 列 = 900 × 2/4 = 450px
第 3 列 = 900 × 1/4 = 225px当 fr 与固定单位混用时,先减去固定部分,剩余空间再按 fr 分配:
css
.container {
width: 900px;
grid-template-columns: 200px 1fr 2fr;
}剩余空间 = 900 - 200 = 700px
第 1 列 = 200px(固定)
第 2 列 = 700 × 1/3 ≈ 233px
第 3 列 = 700 × 2/3 ≈ 467pxrepeat() 函数
避免重复书写相同的轨道定义:
css
.container {
grid-template-columns: repeat(3, 1fr); /* 三列等宽 */
grid-template-columns: repeat(3, 100px 200px); /* 六列:100 200 100 200 100 200 */
grid-template-columns: repeat(auto-fill, 200px); /* 自动填充尽可能多的 200px 列 */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); /* 响应式网格 */
}auto-fill vs auto-fit:
两者在容器宽度大于所有项目总宽度时表现不同:
容器宽度 800px,3 个项目,每个 200px
auto-fill: 创建 4 列(尽可能多的列),第 4 列为空
[Item1 200px] [Item2 200px] [Item3 200px] [空 200px]
auto-fit: 创建 3 列,空列被折叠为 0,多余空间分配给现有列
[Item1 ~267px] [Item2 ~267px] [Item3 ~267px]auto-fill 保留空轨道,auto-fit 折叠空轨道。大多数响应式布局场景使用 auto-fit + minmax() 组合。
minmax() 函数
定义轨道尺寸的范围:
css
.container {
grid-template-columns: minmax(200px, 1fr) minmax(300px, 2fr) minmax(100px, 1fr);
}minmax(min, max) 的含义:轨道尺寸不小于 min,不大于 max。当可用空间不足时取 min,空间充裕时按 max 规则分配。
grid-template-areas
通过命名区域直接定义网格布局,这是 Grid 最具表现力的特性——所见即所得。
css
.container {
display: grid;
grid-template-columns: 200px 1fr 200px;
grid-template-rows: 80px 1fr 60px;
grid-template-areas:
"header header header"
"sidebar content aside"
"footer footer footer";
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.aside { grid-area: aside; }
.footer { grid-area: footer; }┌──────────────────────────────────┐
│ header │ 80px
├────────┬───────────────┬─────────┤
│ │ │ │
│sidebar │ content │ aside │ 1fr
│ │ │ │
├────────┴───────────────┴─────────┤
│ footer │ 60px
└──────────────────────────────────┘
200px 1fr 200px命名区域的规则:
- 每个字符串代表一行,空格分隔各列
- 同名区域必须组成一个矩形(不能是 L 形或 T 形)
- 使用
.表示空白单元格:"header header ."表示第三列为空
命名区域自动生成网格线:定义 grid-area: header 后,会自动生成 header-start 和 header-end 网格线,可以在其他元素中引用。
gap / row-gap / column-gap
控制网格单元格之间的间距(不影响容器边缘)。
css
.container {
gap: 20px; /* 行间距和列间距都是 20px */
gap: 20px 10px; /* 行间距 20px,列间距 10px */
row-gap: 20px; /* 单独设置行间距 */
column-gap: 10px; /* 单独设置列间距 */
}┌──────┐ 10px ┌──────┐ 10px ┌──────┐
│ │ ←───→ │ │ ←───→ │ │
│ │ │ │ │ │
└──────┘ └──────┘ └──────┘
↕ 20px
┌──────┐ ┌──────┐ ┌──────┐
│ │ │ │ │ │
└──────┘ └──────┘ └──────┘justify-items / align-items
控制所有 Grid 项目在其各自单元格内的对齐方式。
css
.container {
justify-items: stretch; /* 默认值:水平方向拉伸填满单元格 */
align-items: stretch; /* 默认值:垂直方向拉伸填满单元格 */
}css
.container {
justify-items: start | end | center | stretch; /* 水平方向 */
align-items: start | end | center | stretch; /* 垂直方向 */
place-items: center; /* 简写:同时设置两个方向 */
}justify-items: start justify-items: center justify-items: end
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│┌───┐ │ │ ┌───┐ │ │ ┌───┐│
││ │ │ │ │ │ │ │ │ ││
│└───┘ │ │ └───┘ │ │ └───┘│
└─────────────────┘ └─────────────────┘ └─────────────────┘justify-items vs justify-content:justify-items 控制项目在单元格内的对齐;justify-content 控制整个网格在容器内的对齐。当网格总尺寸小于容器时,justify-content 才有效果。
justify-content / align-content
当网格总尺寸小于容器尺寸时,控制整个网格在容器内的对齐方式。
css
.container {
justify-content: start | end | center | stretch | space-between | space-around | space-evenly;
align-content: start | end | center | stretch | space-between | space-around | space-evenly;
place-content: center; /* 简写 */
}justify-content: center(网格整体在容器中水平居中)
┌───────────────────────────────┐
│ │
│ ┌─────┬─────┬─────┐ │
│ │ │ │ │ │
│ ├─────┼─────┼─────┤ │
│ │ │ │ │ │
│ └─────┴─────┴─────┘ │
│ │
└───────────────────────────────┘grid-auto-rows / grid-auto-columns
定义隐式创建的轨道大小。当 Grid 项目的数量超出了 grid-template 定义的范围时,浏览器会自动创建新的轨道,grid-auto-* 控制这些隐式轨道的大小。
css
.container {
grid-template-rows: 100px 100px; /* 显式定义 2 行 */
grid-auto-rows: 80px; /* 超出部分每行 80px */
grid-auto-rows: minmax(80px, auto); /* 超出部分最小 80px,自适应 */
}这在数据列表、卡片网格等项目数量不确定的场景中非常有用——你无需预先知道需要多少行。
grid-auto-flow
控制自动放置算法的方向。
css
.container {
grid-auto-flow: row; /* 默认值:先填满一行,再换下一行 */
grid-auto-flow: column; /* 先填满一列,再换下一列 */
grid-auto-flow: row dense; /* 行优先 + 密集填充(回填空洞) */
grid-auto-flow: column dense; /* 列优先 + 密集填充 */
}dense 的作用:默认的自动放置算法是"稀疏"的——如果一个大项目留下了空洞,后续小项目不会回填。dense 关键字启用密集填充算法,允许后续小项目回填前面的空洞。
不用 dense(默认,稀疏): 使用 dense(密集填充):
┌───┬───┬───┐ ┌───┬───┬───┐
│ 1 │ 1 │ │ │ 1 │ 1 │ 3 │ ← 3 回填了空洞
├───┴───┤ │ ├───┴───┤ │
│ 2 │ │ ← 空洞 │ 2 │ │
├───────┼───┤ ├───────┼───┤
│ 3 │ 4 │ │ 4 │ 5 │
└───────┴───┘ └───────┴───┘注意:dense 会改变项目的视觉顺序,可能影响无障碍访问。
项目属性
grid-column / grid-row
通过网格线编号指定项目的放置位置。
css
.item {
grid-column-start: 1;
grid-column-end: 3;
grid-row-start: 1;
grid-row-end: 2;
/* 简写 */
grid-column: 1 / 3; /* 从列线 1 到列线 3(跨 2 列) */
grid-row: 1 / 2; /* 从行线 1 到行线 2(跨 1 行) */
} 列线1 列线2 列线3 列线4
│ │ │ │
┌───────────────┬───────┐ ← 行线 1
│ Item A │ │
│ (1/3, 1/2) │ │ ← 行线 2
├───────┬───────┼───────┤
│ │ │ │ ← 行线 3
└───────┴───────┴───────┘span 关键字
不想数网格线编号?可以用 span 指定跨越的轨道数量:
css
.item {
grid-column: 1 / span 2; /* 从列线 1 开始,跨 2 列 */
grid-row: span 3; /* 从自动位置开始,跨 3 行 */
}负数编号
从末尾计数:
css
.item {
grid-column: 1 / -1; /* 从第 1 列到最后一列(横跨整行) */
}grid-area
grid-area 可以用两种方式:
方式一:配合 grid-template-areas 使用命名区域
css
.item { grid-area: header; }方式二:作为 grid-row-start / grid-column-start / grid-row-end / grid-column-end 的简写
css
.item {
grid-area: 1 / 1 / 3 / 4;
/* 等价于:
grid-row-start: 1;
grid-column-start: 1;
grid-row-end: 3;
grid-column-end: 4;
*/
}justify-self / align-self
允许单个项目覆盖容器的 justify-items / align-items 设置。
css
.item {
justify-self: start | end | center | stretch; /* 水平方向 */
align-self: start | end | center | stretch; /* 垂直方向 */
place-self: center; /* 简写 */
}命名网格线
除了用编号引用网格线,还可以给网格线命名,提高可读性:
css
.container {
grid-template-columns: [sidebar-start] 200px [sidebar-end content-start] 1fr [content-end];
grid-template-rows: [header-start] 80px [header-end main-start] 1fr [main-end footer-start] 60px [footer-end];
}
.sidebar {
grid-column: sidebar-start / sidebar-end;
grid-row: main-start / main-end;
}
.content {
grid-column: content-start / content-end;
}一条网格线可以有多个名称(如 [sidebar-end content-start]),方便不同项目用不同的语义引用同一条线。
隐式网格 vs 显式网格
这是理解 Grid 行为的关键概念。
显式网格:由 grid-template-columns、grid-template-rows、grid-template-areas 明确定义的网格。
隐式网格:当 Grid 项目被放置到显式网格之外时(超出定义的行/列范围),浏览器自动创建的额外轨道。
css
.container {
grid-template-columns: repeat(3, 1fr);
grid-template-rows: 100px 100px; /* 显式 2 行 */
grid-auto-rows: 60px; /* 隐式行高 60px */
}┌─────────┬─────────┬─────────┐
│ │ │ │ 100px ← 显式
├─────────┼─────────┼─────────┤
│ │ │ │ 100px ← 显式
├─────────┼─────────┼─────────┤
│ │ │ │ 60px ← 隐式(grid-auto-rows)
├─────────┼─────────┼─────────┤
│ │ │ │ 60px ← 隐式
└─────────┴─────────┴─────────┘如果不设置 grid-auto-rows,隐式轨道的默认大小是 auto(由内容决定)。
常见布局实战
经典页面布局(Header + Sidebar + Content + Footer)
css
.page {
display: grid;
grid-template-columns: 240px 1fr;
grid-template-rows: 64px 1fr 48px;
grid-template-areas:
"header header"
"sidebar content"
"footer footer";
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.footer { grid-area: footer; }┌──────────────────────────────┐
│ header │ 64px
├─────────┬────────────────────┤
│ │ │
│ sidebar │ content │ 1fr(自适应)
│ 240px │ │
├─────────┴────────────────────┤
│ footer │ 48px
└──────────────────────────────┘响应式改造——移动端变为单列:
css
@media (max-width: 768px) {
.page {
grid-template-columns: 1fr;
grid-template-rows: 64px auto 1fr 48px;
grid-template-areas:
"header"
"sidebar"
"content"
"footer";
}
}响应式卡片网格
无需媒体查询的自适应网格:
css
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 24px;
}宽屏幕:
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ Card │ │ Card │ │ Card │ │ Card │
└──────┘ └──────┘ └──────┘ └──────┘
中屏幕:
┌──────────┐ ┌──────────┐
│ Card │ │ Card │
└──────────┘ └──────────┘
┌──────────┐ ┌──────────┐
│ Card │ │ Card │
└──────────┘ └──────────┘
小屏幕:
┌──────────────────┐
│ Card │
└──────────────────┘
┌──────────────────┐
│ Card │
└──────────────────┘repeat(auto-fit, minmax(280px, 1fr)) 的含义:每列最小 280px、最大 1fr,自动计算能放下多少列。这是实现响应式网格最优雅的方式,一行 CSS 替代多个媒体查询。
仪表盘布局
不规则网格中的精确放置:
css
.dashboard {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: 120px 200px 200px;
gap: 16px;
}
.stat-card { }
.chart-large {
grid-column: span 2;
grid-row: span 2;
}
.chart-small {
grid-column: span 2;
}┌──────┬──────┬──────┬──────┐
│ Stat │ Stat │ Stat │ Stat │ 120px
├──────┴──────┼──────┴──────┤
│ │ │
│ Chart Large │ Chart Large │ 200px
│ │ │
├──────┬──────┼─────────────┤
│ │ │ │
│ │ │ Chart Small │ 200px
└──────┴──────┴─────────────┘等高等宽的九宫格
css
.grid-3x3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
aspect-ratio: 1;
gap: 4px;
}aspect-ratio: 1 让容器保持正方形,配合 1fr 实现完美的九宫格。
瀑布流布局(CSS Grid 近似方案)
纯 CSS Grid 实现瀑布流需要借助 grid-row: span N 模拟不同高度:
css
.masonry {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
grid-auto-rows: 20px;
gap: 16px;
}
.item-small { grid-row: span 8; }
.item-medium { grid-row: span 12; }
.item-large { grid-row: span 16; }局限性:CSS Grid 瀑布流需要预先知道或计算项目高度,无法像 Masonry.js 那样根据内容自动排列。W3C 正在制定原生的 masonry 值(grid-template-rows: masonry),但截至 2025 年仍在实验阶段。
Subgrid
Subgrid 是 Grid Level 2 规范中引入的特性,解决了嵌套网格对齐的问题。
问题:嵌套网格无法对齐
css
.outer {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
}
.inner {
display: grid;
grid-template-columns: 1fr 1fr; /* 独立的网格,与外层无关 */
}内层网格的列与外层网格的列完全独立,无法对齐。
Subgrid 的解决方案
css
.outer {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
}
.inner {
grid-column: span 4;
display: grid;
grid-template-columns: subgrid; /* 继承父网格的列轨道定义 */
}subgrid 让子网格继承父网格的轨道定义,使得子网格的列(或行)与父网格完美对齐。
外层网格:
┌─────┬─────┬─────┬─────┐
│ 1 │ 2 │ 3 │ 4 │
├─────┴─────┴─────┴─────┤
│ 内层 grid (subgrid) │
│ ┌─────┬─────┬─────┬─────┐ ← 列线与外层完全对齐
│ │ A │ B │ C │ D │
│ └─────┴─────┴─────┴─────┘
└───────────────────────┘也可以只在一个方向使用 subgrid:
css
.inner {
display: grid;
grid-template-columns: subgrid; /* 列继承父网格 */
grid-template-rows: 50px 50px; /* 行独立定义 */
}典型场景:卡片列表中让所有卡片的标题行、内容行、底部行对齐——即使各卡片内容长短不一。
Grid 布局中的对齐系统总结
Grid 的对齐属性可能是最容易混淆的部分。这里做一个系统梳理:
│ 容器属性 │ 项目属性
──────────────────┼──────────────────────┼──────────────
水平方向(行轴) │ justify-items │ justify-self
│ justify-content │
──────────────────┼──────────────────────┼──────────────
垂直方向(块轴) │ align-items │ align-self
│ align-content │
──────────────────┼──────────────────────┼──────────────
两个方向(简写) │ place-items │ place-self
│ place-content │| 属性 | 控制对象 | 作用 |
|---|---|---|
justify-items | 所有项目 | 项目在单元格内的水平对齐 |
align-items | 所有项目 | 项目在单元格内的垂直对齐 |
justify-content | 整个网格 | 网格在容器内的水平对齐 |
align-content | 整个网格 | 网格在容器内的垂直对齐 |
justify-self | 单个项目 | 覆盖 justify-items,项目在单元格内的水平对齐 |
align-self | 单个项目 | 覆盖 align-items,项目在单元格内的垂直对齐 |
记忆技巧:
justify-*→ 行轴方向(水平)align-*→ 块轴方向(垂直)*-items/*-self→ 项目在单元格内的对齐*-content→ 网格在容器内的对齐
经典面试题解析
题目一:Grid 和 Flex 应该怎么选?
一维排列用 Flex,二维布局用 Grid,这是最基本的判断标准。
更精细的选择:
| 场景 | 推荐 | 原因 |
|---|---|---|
| 导航栏 | Flex | 一维水平排列,项目数量可能变化 |
| 卡片列表 | 两者皆可 | Flex + wrap 或 Grid + auto-fit |
| 整体页面结构 | Grid | 二维布局,header/sidebar/content/footer |
| 表单布局 | Grid | label 和 input 的对齐 |
| 居中一个元素 | 两者皆可 | Flex: justify+align / Grid: place-items |
| 仪表盘 | Grid | 不规则的二维放置 |
| 工具栏按钮组 | Flex | 一维排列 + 动态间距 |
它们可以嵌套使用:外层 Grid 定义页面骨架,内层 Flex 处理各区域内部的排列。
题目二:repeat(auto-fit, minmax(200px, 1fr)) 是如何实现响应式的?
逐步拆解:
minmax(200px, 1fr):每列最小 200px,最大可占 1 个弹性份额auto-fit:自动计算能放下多少列- 浏览器计算过程:
- 容器宽度 = 900px,gap = 20px
- 尝试放 5 列:5 × 200 + 4 × 20 = 1080 > 900 → 不行
- 尝试放 4 列:4 × 200 + 3 × 20 = 860 ≤ 900 → 可以
- 剩余空间 = 900 - 860 = 40px → 按 1fr 分配
- 每列实际宽度 = 200 + 40/4 = 210px
- 当容器变窄到 < 420px 时,只能放 1 列,卡片占满宽度
整个过程不需要任何媒体查询。
题目三:什么是网格线编号?为什么从 1 开始?
网格线是轨道之间的分界线,编号从 1 开始(不是从 0)。一个 3 列 × 2 行的网格有 4 条列线和 3 条行线:
1 2 3 4 ← 列线编号
│ │ │ │
1 ──┌─────┬─────┬─────┐
│ │ │ │
2 ──├─────┼─────┼─────┤
│ │ │ │
3 ──└─────┴─────┴─────┘
↑
行线编号从 1 开始是因为网格线定义的是边界,第 1 条线是网格的起始边界。也可以用负数从末尾计数:-1 是最后一条线,-2 是倒数第二条。
grid-column: 1 / -1 表示"从第一条线到最后一条线",即横跨整行——这是 Grid 中最常用的"全宽"写法。
题目四:auto-fill 和 auto-fit 在什么情况下表现不同?
只有当项目数量不足以填满一行时才有区别。
css
.container {
width: 600px;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
/* 假设只有 2 个项目 */auto-fill:创建尽可能多的列(6列),即使有空列。项目按 minmax 规则,每列 100px,空列也占 100px。
auto-fit:同样计算出 6 列,但将空列折叠为 0px。由于 max 是 1fr,多余空间分配给有内容的列,2 个项目各占 300px。
实际开发:大多数场景使用 auto-fit,因为你通常希望项目填满可用空间。auto-fill 适用于需要保持固定列宽、不希望项目拉伸的场景。
题目五:grid-template-areas 有哪些限制?
- 命名区域必须是矩形——不能是 L 形、T 形等非矩形区域
- 所有行的列数必须相同
- 区域名称不能重复用于不连续的位置
css
/* ❌ 错误:header 区域不是矩形 */
grid-template-areas:
"header header ."
"header sidebar content";
/* ✅ 正确 */
grid-template-areas:
"header header header"
"sidebar content content";追问思考
- 为什么说 Grid 是"布局驱动"而 Flex 是"内容驱动"?能举一个实际场景说明吗?
grid-template-columns: 1fr 1fr 1fr和grid-template-columns: repeat(3, 1fr)有没有区别?- 当容器宽度无法整除列数时,
fr单位的计算会不会有精度问题?浏览器如何处理? - Subgrid 解决了什么实际问题?没有 Subgrid 之前是怎么处理嵌套网格对齐的?
grid-auto-flow: dense在什么业务场景下适合使用?它的无障碍风险是什么?