写在前面
上一篇文章我们聊了 Flex------它解决的是一维布局问题:沿着一条主轴排布元素。但真实的网页排版几乎都是二维的:既要管横排、又要管竖排;既要有固定的侧栏,又要有自适应的主区;既要有跨多列的标题,又要有多行多列的卡片墙。过去遇到这种需求,要么用 Flex 嵌套(一层套一层,DOM 变臃肿),要么退回 float 加各种 hack。
CSS Grid 是第一个真正为布局设计 的 CSS 模块:它允许你同时定义行和列,把页面划分成规则的二维网格,再让元素精确地"落"到网格的任意位置------甚至跨越多个单元格。它与 Flex 并不对立:Flex 管一维(组件内部排布),Grid 管二维(页面级骨架),二者配合才是现代 CSS 布局的完整答案。
Grid 已被所有主流浏览器完整支持多年,是"基础能力"而非"实验特性"。本文从核心概念讲到实战案例与踩坑,配合可运行代码,帮你把 Grid 一次学透。
一、Grid 的核心概念:先理解"网格"长什么样
Grid 的核心是一套几何词汇,先花两分钟搞懂它们,后面所有属性都会变得顺理成章。
1.1 六大基础术语
┌─────────┬─────────┬─────────┐
│ 网格单元 │ 网格单元 │ 网格单元 │ ← 第1行
├─────────┼─────────┼─────────┤
│ 网格单元 │ 网格单元 │ 网格单元 │ ← 第2行
└─────────┴─────────┴─────────┘
↑第1列 ↑第2列 ↑第3列
| 术语 | 英文 | 说明 |
|---|---|---|
| 网格容器 | Grid Container | 设置 display: grid 的元素 |
| 网格项目 | Grid Item | 容器的直接子元素 |
| 网格轨道 | Grid Track | 一条行或一条列(用 grid-template-rows/columns 定义) |
| 网格线 | Grid Line | 轨道之间的分界线,用于定位元素 |
| 网格单元 | Grid Cell | 行与列交叉出的最小格子 |
| 网格区域 | Grid Area | 一个或多个单元格组成的矩形区域 |
| 网格间隙 | Grid Gap | 轨道之间的间距 |
1.2 网格线的编号规则
网格线是从 1 开始编号的。以 3 列网格为例,垂直方向有 4 条网格线(1~4),水平方向同理。
第1线 第2线 第3线 第4线
│ │ │ │
├──────┼──────┼──────┤
│ │ │ │
元素跨列就是"从第几条线到第几条线"。注意:网格线编号从左到右、从上到下是 1, 2, 3...;从右到左、从下到上则是 -1, -2, -3... ,负号编号在不知道网格有多少条线时非常有用(比如"贴最右边"就是 -1)。
1.3 显式网格 vs 隐式网格
- 显式网格 :你通过
grid-template-columns/rows明确定义的轨道。 - 隐式网格:项目数量超出显式网格时,Grid 自动创建的那些额外轨道。
理解这两者的区别,是后面理解 grid-auto-rows、grid-auto-flow 的前提。
二、开启 Grid:display 的两种形态
css
/* 容器占满整行(块级) */
display: grid;
/* 容器表现为行内元素,但内部仍是网格布局 */
display: inline-grid;
html
<div class="grid">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
</div>
css
.grid {
display: grid;
grid-template-columns: 100px 100px; /* 两列,每列 100px */
}
设置 display: grid 后,项目的默认行为:
- 子元素变成网格项目,默认占一个网格单元。
- 默认只有一列,会撑满容器宽度(不写
grid-template-columns时,项目按行堆叠,类似 block)。 - 项目不再受
float、vertical-align影响。 - 项目默认按"先行后列"的顺序自动放置(
grid-auto-flow: row)。
三、容器属性:搭建网格的"图纸"
容器属性负责"画出网格线"。这是 Grid 与 Flex 最大的不同------先定义结构,再放元素。
3.1 grid-template-columns / grid-template-rows:定义轨道
这是 Grid 最核心的属性,用空格分隔的一组值来定义多条轨道。
css
.grid {
display: grid;
grid-template-columns: 100px 200px 100px; /* 3 列,宽 100/200/100 */
grid-template-rows: 80px 80px; /* 2 行,各 80px */
}
html
<div class="grid">
<div>1</div><div>2</div><div>3</div>
<div>4</div><div>5</div><div>6</div>
</div>
3.2 fr 单位:弹性轨道(Grid 的灵魂)
fr(fraction)表示"剩余空间的份额",是 Grid 最常用的单位。它类似 Flex 里的 flex-grow,但作用于整个轨道。
css
.grid {
display: grid;
grid-template-columns: 1fr 2fr 1fr; /* 三等分宽 → 中间列是两侧的 2 倍 */
}
1fr 2fr 1fr 的含义:容器宽度分成 4 份,左右各占 1 份,中间占 2 份。fr 只在分配剩余空间,所以可以和固定值混用:
css
.grid {
grid-template-columns: 240px 1fr; /* 左栏固定 240px,右栏吃满剩余 */
}
3.3 repeat() 函数:批量生成相同轨道
css
.grid {
grid-template-columns: repeat(3, 1fr); /* 等价于 1fr 1fr 1fr */
grid-template-columns: repeat(4, 100px); /* 4 列,各 100px */
}
repeat() 还支持更灵活的写法:
css
/* 重复一个片段:repeat(2, 1fr 2fr) → 1fr 2fr 1fr 2fr */
.grid {
grid-template-columns: repeat(2, 1fr 2fr);
}
3.4 minmax():轨道尺寸的"弹性范围"
minmax(min, max) 让轨道在最小值和最大值之间弹性伸缩,是做响应式布局的关键。
css
.grid {
grid-template-columns: repeat(3, minmax(150px, 1fr));
/* 每列至少 150px,最多 1fr(平均分配) */
}
minmax 与 auto-fit/auto-fill 组合,能实现"列数随容器宽度自动增减"的经典效果(见实战篇)。
3.5 grid-template-areas:用命名区域"画"布局
这是 Grid 最直观的用法:给每个单元格起名字,然后用 ASCII 图一样的写法描述布局。
css
.grid {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 60px 1fr 60px;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
html
<div class="grid">
<header class="header">头部</header>
<aside class="sidebar">侧栏</aside>
<main class="main">主内容</main>
<footer class="footer">页脚</footer>
</div>
规则要点:
- 同一行的名称用空格隔开,同一列的名称上下对齐。
- 用
.(点)表示空白单元格。 - 每个命名区域必须是一个连续的矩形(不能是 L 形或分离的)。
- 未命名区域的元素会按自动放置规则找位置。
css
/* 带空白的写法:中间挖空 */
grid-template-areas:
"a . b"
"a . b";
3.6 grid-auto-rows / grid-auto-flow:控制隐式网格
当项目超过显式网格时,Grid 自动创建隐式轨道,其尺寸由 grid-auto-rows / grid-auto-columns 控制,排列方向由 grid-auto-flow 控制。
css
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 只定义了 3 列 */
grid-auto-rows: 100px; /* 隐式行统一 100px */
grid-auto-flow: row; /* 默认:先填行,再换下一行 */
}
html
<!-- 12 个项目,显式网格只有 3 列 × 1 行,其余 9 个自动进入隐式行 -->
<div class="grid">
<div>1</div><div>2</div>...<div>12</div>
</div>
grid-auto-flow 可选值:
| 值 | 说明 |
|---|---|
row(默认) |
先填满一行,再往下换行 |
column |
先填满一列,再往右换列 |
row dense / column dense |
允许"回填"空隙,让布局更紧凑(可能会打乱顺序) |
dense 适合图片画廊这类"不介意顺序、只求紧凑"的场景;要求元素按顺序排布的布局不要用。
3.7 gap:轨道间距
与 Flex 的 gap 完全一致,无需 margin hack。
css
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px; /* 行与列统一 16px */
/* row-gap / column-gap 可分别设置 */
}
3.8 justify-items / align-items:单元格内的对齐
与 Flex 的 align-items 类似,但作用于每个网格单元内部 :justify-items 管水平(行轴),align-items 管垂直(列轴)。
css
.grid {
justify-items: stretch; /* 默认,项目拉伸填满单元格 */
justify-items: center;
justify-items: start;
justify-items: end;
align-items: stretch; /* 默认 */
align-items: center;
}
css
/* 让所有项目在各自单元格内水平垂直居中 */
.grid {
justify-items: center;
align-items: center;
}
3.9 justify-content / align-content:网格整体的对齐
当网格总尺寸小于容器 时,用它们控制网格整体在容器中的位置(类似 Flex 的 justify-content)。
css
.grid {
grid-template-columns: 200px 200px; /* 总宽 400px < 容器宽 */
justify-content: center; /* 网格整体水平居中 */
align-content: center; /* 网格整体垂直居中 */
}
可选值:start / center / end / space-between / space-around / space-evenly。
3.10 grid-template 简写
grid-template 是 grid-template-rows + grid-template-columns + grid-template-areas 的合并简写:
css
.grid {
grid-template:
"header header" 60px
"sidebar main" 1fr
"footer footer" 60px
/ 200px 1fr;
}
/* 等价于分开写 rows / columns / areas */
四、项目属性:让元素"落"到网格里
容器画好网格线之后,项目属性负责指定每个元素占据哪些单元格。
4.1 grid-column / grid-row:按网格线编号定位
语法:grid-column: <起始线> / <结束线>(grid-row 同理)。
css
.item {
grid-column: 1 / 3; /* 从第 1 条线到第 3 条线 → 跨 2 列 */
grid-row: 1 / 2; /* 只占第 1 行 */
}
html
<div class="grid">
<div class="banner">banner 跨两列</div>
<div>其他项目自动排</div>
<div>其他项目自动排</div>
</div>
css
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.banner { grid-column: 1 / 3; }
用 span 关键字表达"跨几格",语义更清晰:
css
.banner {
grid-column: span 2; /* 跨 2 列(自动选择起点) */
grid-row: span 1; /* 占 1 行 */
}
用负编号"贴边":
css
.right-panel {
grid-column: -2 / -1; /* 从右数第 2 条线到最右线 */
}
4.2 grid-area:命名区域 / 四值简写
grid-area 有两种用法:
用法一:引用命名区域 (配合容器的 grid-template-areas):
css
.header { grid-area: header; }
用法二:四值简写 ,顺序为 row-start / column-start / row-end / column-end:
css
.item {
grid-area: 1 / 1 / 3 / 3; /* 从第1行第1列 到 第3行第3列 → 2×2 区域 */
}
4.3 justify-self / align-self:单个项目的单元格内对齐
覆盖容器的 justify-items / align-items,只作用于当前项目。
css
.item {
justify-self: center; /* 水平居中 */
align-self: end; /* 贴单元格底部 */
}
4.4 order:调整项目顺序
与 Flex 的 order 语义一致,数值越小越靠前。
css
.item {
order: -1; /* 排到最前 */
}
五、实战:6 个高频布局一次写清
5.1 三行代码搞定经典栅格
这是 Grid 最惊艳的地方------一个等宽栅格只需要三行:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<style>
body { margin: 0; font-family: system-ui, sans-serif; }
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
padding: 24px;
}
.card { background: #f0f4ff; border: 1px solid #d6e0ff; border-radius: 8px; padding: 20px; text-align: center; }
</style>
</head>
<body>
<div class="grid">
<div class="card">1</div><div class="card">2</div><div class="card">3</div>
<div class="card">4</div><div class="card">5</div><div class="card">6</div>
</div>
</body>
</html>
5.2 12 栅格系统(响应式框架的基础)
很多 UI 框架的栅格,本质就是 Grid 的封装:
css
.grid-12 {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 16px;
}
.col-3 { grid-column: span 3; } /* 占 3/12 = 1/4 宽 */
.col-4 { grid-column: span 4; } /* 占 1/3 宽 */
.col-6 { grid-column: span 6; } /* 占 1/2 宽 */
.col-8 { grid-column: span 8; }
.col-12 { grid-column: span 12; }
html
<div class="grid-12">
<div class="col-4 card">col-4</div>
<div class="col-4 card">col-4</div>
<div class="col-4 card">col-4</div>
<div class="col-6 card">col-6</div>
<div class="col-6 card">col-6</div>
</div>
5.3 圣杯布局(Grid 版)------对比上一篇文章的 Flex 版
html
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; }
.layout {
display: grid;
grid-template-columns: 200px 1fr 200px;
grid-template-rows: 60px 1fr 60px;
grid-template-areas:
"header header header"
"left main right"
"footer footer footer";
min-height: 100vh;
}
.header { grid-area: header; background: #333; color: #fff; padding: 16px; }
.left { grid-area: left; background: #f5f5f5; padding: 16px; }
.main { grid-area: main; background: #fff; padding: 16px; }
.right { grid-area: right; background: #f5f5f5; padding: 16px; }
.footer { grid-area: footer; background: #333; color: #fff; padding: 16px; text-align: center; }
</style>
<div class="layout">
<header class="header">头部</header>
<aside class="left">左栏 200px</aside>
<main class="main">中间自适应</main>
<aside class="right">右栏 200px</aside>
<footer class="footer">页脚</footer>
</div>
对比 Flex 版:Flex 需要 flex: 1 + 固定宽度 + 换行处理;Grid 用 grid-template-areas 一眼就能看出整体结构,且DOM 顺序可以完全独立于视觉位置 (比如把 main 放在 HTML 最前面也照样渲染到中间)。
5.4 Dashboard 面板布局(跨行跨列)
Grid 处理"区域大小不一"的面板布局最拿手:
html
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; padding: 24px; background: #f7f8fa; }
.dashboard {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: 120px 120px;
grid-auto-rows: 120px; /* 超出部分也统一 120px */
gap: 16px;
}
.panel { background: #fff; border: 1px solid #eee; border-radius: 8px; padding: 16px; display: flex; align-items: center; justify-content: center; color: #555; }
.wide { grid-column: span 2; }
.tall { grid-row: span 2; }
.hero { grid-column: span 2; grid-row: span 2; background: #4f8cff; color: #fff; }
</style>
<div class="dashboard">
<div class="panel hero">主视觉(2×2)</div>
<div class="panel">KPI 1</div>
<div class="panel">KPI 2</div>
<div class="panel wide">图表(跨 2 列)</div>
<div class="panel tall">侧栏(跨 2 行)</div>
<div class="panel">列表</div>
</div>
5.5 响应式画廊:auto-fit + minmax 自动换列
这是 Grid 最惊艳的能力之一------不写媒体查询,列数自动随容器宽度变化:
html
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; padding: 24px; background: #f7f8fa; }
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 12px;
}
.photo { background: #4f8cff; color: #fff; border-radius: 8px; aspect-ratio: 1; display: flex; align-items: center; justify-content: center; }
</style>
<div class="gallery">
<div class="photo">1</div><div class="photo">2</div><div class="photo">3</div>
<div class="photo">4</div><div class="photo">5</div><div class="photo">6</div>
</div>
试试缩放窗口:容器宽时一排放 56 张,窄时自动收缩到 2 3 张,无需任何媒体查询。
auto-fill vs auto-fit 的区别(重要):
auto-fill:尽可能多地创建轨道,即使空着也算数。auto-fit:创建轨道后,空轨道会被折叠,让内容轨道拉伸铺满。
css
/* auto-fill:容器很宽时右侧会有空白列(轨道空着) */
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
/* auto-fit:空轨道被折叠,项目铺满整行 */
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
当项目数量少、希望铺满整行时用 auto-fit;希望保持固定轨道数量时用 auto-fill。
5.6 网格居中
Grid 也能做水平垂直居中(和 Flex 一样简单):
css
.center {
display: grid;
place-items: center; /* justify-items + align-items 的简写 */
}
html
<div class="center" style="height: 100vh;">
<div>居中的内容</div>
</div>
place-items 是 justify-items 和 align-items 的合并简写,一行代码搞定双轴居中。
六、Grid vs Flex:什么时候用哪个?
承接上一篇文章的结论:Flex 是一维,Grid 是二维。更准确的表述是------
| 维度 | Flexbox | CSS Grid |
|---|---|---|
| 布局模型 | 一维(沿主轴) | 二维(行 + 列同时) |
| 设计思路 | 内容驱动:项目沿主轴流动、自动伸缩 | 结构驱动:先定义网格线,再放置元素 |
| 元素定位 | 靠顺序 + 对齐,难精确控制位置 | 可精确指定行列范围、跨行跨列 |
| 典型场景 | 导航、居中、列表、组件内部排布 | 页面骨架、栅格系统、Dashboard、画廊 |
| 重叠元素 | 不擅长 | 用 grid-area 让元素占同一区域可叠加 |
| 自动换行 | 需 flex-wrap + 宽度计算 |
repeat(auto-fit, minmax(...)) 一行搞定 |
选择建议:
- 只是横向或纵向的排布(导航、按钮组、列表行、垂直居中)→ Flex。
- 需要同时管行列、需要元素跨行跨列、需要"画"出整个页面结构 → Grid。
- 组件内部用 Flex 排细节,页面级用 Grid 定骨架 → 两者结合,这是现代 CSS 布局的标准姿势。
七、高频踩坑与最佳实践
7.1 坑 1:隐式网格尺寸"失控"
只定义了 grid-template-columns,没定义行高,超出部分的行默认是 auto(由内容撑开),看起来会参差不齐。
css
/* 只写了列,行是隐式的 */
.grid { grid-template-columns: repeat(3, 1fr); }
/* 修复:给隐式行统一尺寸 */
.grid { grid-template-columns: repeat(3, 1fr); grid-auto-rows: 100px; }
7.2 坑 2:auto-fill 与 auto-fit 用错
项目少、希望铺满时用了 auto-fill,会出现右侧空白列;项目多、希望换行时两者差异不大。不确定时优先 auto-fit(更符合"铺满"直觉)。
7.3 坑 3:fr 与 % / px 混用的陷阱
fr 分配的是剩余空间 。100px 1fr 是"100px 固定,剩余给 1fr";但 50% 1fr 则是"50% 固定,1fr 拿剩下的 50%"------两列永远不满容器(除非 1fr 恰好等于 50%)。需要等分时直接用 repeat(2, 1fr) 或 repeat(2, minmax(0, 1fr))。
7.4 坑 4:minmax 最小值和内容溢出
minmax(150px, 1fr) 中的最小值是硬下限,如果容器比 150px × 列数 还窄,网格会溢出。小屏防护:
css
/* 用 minmax(0, 1fr) 允许轨道缩到 0 */
.grid { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); }
7.5 坑 5:grid-template-areas 的矩形限制
命名区域必须连续、必须矩形,不能出现 L 形或断开的同名区域。需要复杂形状时改用 grid-column / grid-row 的编号定位。
7.6 坑 6:网格线编号方向
网格线从 1 开始,负号从终点往回数。容易记混的是 grid-column: 1 / 3 是"跨 2 列"(3-1=2),而不是占 3 列。用 span 表达跨格数更不容易出错。
7.7 最佳实践小结
- 页面骨架用 Grid,组件内部用 Flex,各司其职。
- 轨道尺寸优先用
fr和minmax(),少用百分比。 - 能用
grid-template-areas就不手写编号,可读性最高。 - 响应式优先
auto-fit + minmax(0, 1fr),大部分情况能免掉媒体查询。 - 给隐式网格统一
grid-auto-rows,避免尺寸失控。 - 间距一律
gap,别再用 margin hack。
八、总结
Grid 的学习路径比 Flex 多一个层次:Flex 只需要"容器管排布、项目管伸缩",Grid 还需要"先画网格线、再定位元素"。但也正因为"结构先行",Grid 表达复杂布局的能力远超 Flex。
把整篇文章收敛成几条主线:
- 开网格 :
display: grid。 - 画轨道 :
grid-template-columns/rows,配合repeat()、fr、minmax()。 - 命名区域 :
grid-template-areas+grid-area,可读性之王。 - 隐式网格 :超出部分用
grid-auto-rows/grid-auto-flow控制。 - 放元素 :
grid-column/grid-row/grid-area,用span表达跨格。 - 对齐 :单元格内
justify/align-items(简写place-items),整体justify/align-content。 - 间距 :一律
gap。 - 响应式 :
repeat(auto-fit, minmax(0, 1fr))一行搞定。
Flex 解决一维的"排",Grid 解决二维的"布"------把这两件工具用熟,再加上几行媒体查询收尾,绝大多数网页排版都能轻松拿下。