CSS Grid 这东西,我一开始是拒绝的。用 flex 用了好几年,左右两栏、水平居中、垂直居中都靠 flex 一把梭,凭什么要我再学一套新东西?直到有一天我需要实现一个带表头、表尾、中间内容区还要自适应滚动,同时侧边栏要跨两行的复杂后台管理页,flex 嵌套了三层之后代码已经乱得自己都看不下去,我咬着牙试了一次 Grid,当场就真香了。
Grid 和 flex 最大的区别在于维度认知。flex 是一维排列,你只能选主轴方向(水平或者垂直)去排一排东西;而 Grid 是二维网格,你直接在一张"表"里同时控制行和列。拿生活里的场景打比方:flex 像一串手串,珠子按顺序串好就行;Grid 像一张 Excel 表格,你可以任意决定某个单元格横跨几行几列。所以只要你需要在两个方向同时做布局,比如导航栏+侧边栏+内容区这种后台铁三角、商品卡片墙、图片九宫格,Grid 的代码量大概是 flex 的零头,而且语义清楚得多。
这篇文章我打算把 Grid 里最常用、真正管用的那部分直接拆开讲,从容器属性、项目属性,到六种我反复在项目里使用的经典布局模板,再到我踩过的坑。不管你是刚接触 CSS 的初学者,还是 flex 老手想升级到 Grid,看完这篇基本可以直接上手。
1. Grid 布局的整体设计思路:为什么它比 flex 更合适"复杂布局"
很多人对 Grid 的第一印象是"声明式"很强:你不用告诉我这个元素先排谁后排谁,你只需要告诉我整个网格长什么样子,浏览器自己会把所有子项放进去。这其实就是 Grid 和 flex 最深层的区别:flex 是内容驱动,内容一个个排队进容器;Grid 是容器驱动,容器先把轨道划分好,内容自动往里填。
1.1 从"排列方式"转向"网格模板"
用 flex 做三栏布局,标准写法是左侧固定宽度、中间 flex: 1、右侧固定宽度,然后用 flex-wrap 处理换行。这个方案能用,但有一个致命弱点:flex 环境下,子项的总宽度一旦超过容器,换行规则是不可预测的,只能靠百分比和 flex-basis 来回试。而 Grid 用 grid-template-columns 直接定义每一列宽度,比如:
css复制.grid-3col {
display: grid;
grid-template-columns: 200px 1fr 200px;
}
这就等于我明确告诉浏览器:左边 200px,右边 200px,中间占剩余所有空间。不存在"超了怎么办",因为 fr 单位天生就是为了处理剩余空间的。
1.2 Grid 的适用场景边界
当然 Grid 也不是万能的。我个人的判断标准很简单:如果布局只在单方向上有逻辑,用 flex;如果项目到横竖两个方向都需要对齐和跨度控制,无脑 Grid。比如导航栏里一排按钮,按钮之间间距又不同,flex 的 gap 配合 justify-content 就够了,没必要上 Grid。但如果是"左边头像+右边信息和操作按钮"这种组合,虽然看起来像一维,实际上你对齐很容易乱,Grid 的 grid-template-columns: 80px 1fr 一行就搞定了。
1.3 网格思维的关键:轨道、线、单元格
理解 Grid,不要去想"选择器",去想"轨道"和"线"。grid-template-columns 定义的是垂直方向的轨道集合,grid-template-rows 定义的是水平方向。相邻两条轨道之间的线就是网格线,网格线编号从 1 开始。项目可以占据一条或几条轨道,通过 grid-column: 1 / 3 表示从第 1 条线到第 3 条线,也就是横跨两列。这个概念一开始有点抽象,但你可以把它想象成 Excel 的合并单元格,用久了之后反而觉得 flex 的表达能力太弱了。
提示:如果你熟悉 flex,上手 Grid 最大的障碍是"忘记内容存在"。不要一开始就考虑有多少个子元素,而是先画草图:这个页面一共有几行几列,哪些区域要合并。画完之后 Grid 代码基本就出来了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 必须掌握的核心属性与单位用法
Grid 的属性很多,但真正高频的其实就几个。我按"容器上的"和"项目上的"两类来梳理,每个属性附上使用场景,方便你对照。
2.1 容器上的关键属性
先说最核心的 grid-template-columns 和 grid-template-rows。列定义、行定义,任你自由组合。比如:
css复制.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto 1fr auto;
gap: 16px;
}
repeat() 函数让重复轨道变得简单,特别适合 12 列栅格。gap 是 grid-gap 的现代写法,它一次性控制了行列间距,不用像 margin 那样反复清。
然后是 grid-template-areas。这个属性是我觉得 Grid 里最"可视化"的写法,你直接画一个 ASCII 图形来表示页面区域:
css复制.container {
display: grid;
grid-template-areas:
"header header header"
"sidebar content content"
"footer footer footer";
grid-template-columns: 220px 1fr 1fr;
grid-template-rows: 60px 1fr 80px;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.footer { grid-area: footer; }
这套写法的优点是:样式一眼就能看出整个页面骨架,改布局只需要调整字符串里的位置,不用动每个子元素。曾经帮同事重构一个后台项目,就是用 grid-template-areas 把三层嵌套的 flex 精简成了二十几行 Grid,他看完当场就决定下一期全部迁移。
2.2 项目上的关键属性
项目(直接子元素)本身也有几个控制位置和大小的属性:grid-column 和 grid-row。常见用法是使用 span 关键字来合并轨道:
css复制.item {
grid-column: span 2;
grid-row: span 3;
}
这表示该元素横跨两列、三行。注意 span 后面跟的是轨道数量,不是网格线编号,对于新手来说比 1 / 3 这种写法更直观。
还有 justify-self 和 align-self,它们控制在单元格内部的对齐。默认情况 Grid 子元素是被拉伸填满单元格的,如果不想拉伸,可以用这两个属性单独覆盖:
css复制.item {
justify-self: center;
align-self: center;
}
2.3 fr 单位、minmax() 和 auto-fit 的配合
fr 单位是 Grid 的核心,它代表"剩余空间的一份"。grid-template-columns: 1fr 2fr 表示第二列宽度是第一列的两倍。但这个单位有个坑:如果内容设置了 min-width 或者内容本身特别长(比如中文长单词),fr 会被内容的 min-content 撑破,导致列宽超出预期。
解决办法是用 minmax(0, 1fr):
css复制grid-template-columns: repeat(3, minmax(0, 1fr));
minmax(0, 1fr) 让轨道的最小值为 0,不再受内容最小宽度影响。这个技巧在我做卡片布局的时候几乎每次都用,因为图片和长文本真的是溢出重灾区。
auto-fit 和 minmax 一起使用可以实现"自适应列数",不需要写死每列宽度:
css复制.container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 20px;
}
容器宽时自动多列,窄时自动少列,媒体查询都省了。auto-fit 与 auto-fill 的区别在于,auto-fit 会收缩空轨道,让列填满整个容器;auto-fill 即使轨道为空也会保留宽度。做响应式栅格,我一般直接用 auto-fit。
提示:grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)) 是响应式布局的"魔法一行",理解之后能取代 80% 的媒体查询。
3. 六种最常用的 Grid 布局,直接抄作业
这里我整理了项目中使用频率最高的六个 Grid 布局场景。每个场景都有完整代码和注释,你可以直接复制到自己的项目里改参数。
3.1 水平垂直居中(最干净的居中方案)
flex 居中已经很方便了,但 Grid 的 place-items: center 其实是更简洁的写法,因为它是 justify-items 和 align-items 的合并属性,同时控制水平和垂直居中:
css复制.center {
display: grid;
place-items: center;
min-height: 100vh;
}
实际工作里我用这个写法做全屏 loading、弹窗遮罩、空状态提示。注意这段代码有一个隐藏效果:Grid 的所有直接子元素都会被居中,如果你的容器里有多个子元素,它们会重叠在同一个单元格,而不是像 flex 那样依次排列。
3.2 左右两栏布局(固定侧栏+自适应主区)
这是博客后台、管理面板、资料展示页最常见的布局形式。左侧栏 280px,右侧主体占据剩余空间:
css复制.two-column {
display: grid;
grid-template-columns: 280px 1fr;
min-height: 100vh;
}
.sidebar {
padding: 20px;
background: #f5f5f5;
}
.main {
padding: 24px;
overflow: auto;
}
这里给容器加 min-height: 100vh 是为了让侧边栏和主体等高撑满屏幕。如果页面内容很长,需要滚动整个文档,就不要加这行,让 Grid 高度自适应即可。
3.3 圣杯三栏布局(中间自适应,两边固定)
圣杯布局是经典中的经典,Grid 解法极其优雅。难点在于中间列在 HTML 里优先渲染,但 CSS 里位置完全由 Grid 控制:
html复制<div class="holy-grail">
<header class="header">Header</header>
<main class="main">Main Content</main>
<aside class="left">Left Sidebar</aside>
<aside class="right">Right Sidebar</aside>
<footer class="footer">Footer</footer>
</div>
css复制.holy-grail {
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; }
.main { grid-area: main; }
.left { grid-area: left; }
.right { grid-area: right; }
.footer { grid-area: footer; }
模板字符串里的区域名称完全对应用口头语言描述的页面框架,下一次维护的时候不用再通过看代码脑补布局了。
3.4 自适应卡片网格(图片墙/商品列表)
做图片墙、视频卡片、商品列表这种东西,Grid 的 auto-fit + minmax 是杀手锏。只要一行,就能在桌面端铺满 5 列、平板 3 列、手机上 1 列,全程无媒体查询:
css复制.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 20px;
}
配合图片注意一点:卡片里的图片默认会被拉"变样",因为 Grid 单元格默认会自动拉伸子元素。图片要用 object-fit: cover 处理:
css复制.card-grid img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 8px;
}
如果有行高不统一问题,可以用 grid-auto-rows: 240px 固定卡片区域高度,或者用 grid-auto-rows: minmax(200px, auto) 让矮的矮、高的高,各自内容撑开所在行。
3.5 简易 12 列栅格系统
很多 UI 框架里都有 12 列栅格,Grid 原生就能实现一套轻量级栅格,不需要依赖框架。比如你希望列宽按 12 格划分,直接用模板字符串拼:
css复制.grid-12 {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 16px;
}
.col-3 { grid-column: span 3; }
.col-4 { grid-column: span 4; }
.col-6 { grid-column: span 6; }
.col-8 { grid-column: span 8; }
.col-12 { grid-column: span 12; }
不用预处理器也能写。如果你在 React 或者 Vue 项目里,通过循环处理类名生成,这样响应式只需要在小屏覆盖 span 的值即可。
3.6 底部吸底 Footer(内容不满屏时,Footer 贴底)
这是 Grid 布局最让我觉得省心的场景。过去用 flex 得在 body 上设置 display: flex; flex-direction: column; min-height: 100vh,再让 main 区域 flex: 1。现在用 Grid 直接在根容器上做:
css复制.page {
display: grid;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
.site-header { grid-row: 1; }
.site-main { grid-row: 2; }
.site-footer { grid-row: 3; }
main 占据了中间剩余空间,内容不足时也不会把 footer 顶上去,而是让 footer 始终贴着屏幕底部。这个方案在文档流里很稳,不需要给 body 额外加任何 hack。
4. 实战中一定会踩的坑,以及排查思路
Grid 优点多,但坑也不少。这一节我整理了自己在真实项目里踩过的几个高频问题,每个都给了排查方向和最终解决方案。
4.1 子元素总宽度溢出,比容器还宽(fr 的陷阱)
这个问题出现在给 Grid 内部放长文本、长 URL、图片的时候。比如:
css复制.grid {
display: grid;
grid-template-columns: 1fr 1fr;
}
如果其中一格塞了一个不换行的英文长串,它会把这一列撑得超出容器。原因:fr 轨道的默认最小尺寸是 auto,内容不会被截断,而是硬撑宽度。解决办法是对溢出元素设置 min-width: 0,或者把轨道换成 minmax(0, 1fr):
css复制.grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid .long-content {
word-break: break-word;
}
4.2 使用 grid-template-areas 时区域名称必须保持一致
grid-area 名称的大小写敏感且不允许重复拷贝。区域字符串里的每个单元格都要有名字,可以用 . 表示空单元格:
css复制grid-template-areas:
"header header"
"sidebar ."
"footer footer";
中间的 . 表示这个单元格不分配给任何区域。如果你忘了写 . 或者区域名称不一致,整个 grid-template-areas 会被判定为无效,布局直接不生效。我在项目里曾经因为复制粘贴导致区域名多打了一个空格,整整排查了半个小时,所以如果你的 Grid 布局莫名失效,优先检查区域名称是否匹配、空单元格是否用 . 标注。
4.3 子元素被拉伸,哪哪都不对
Grid 默认 justify-items: stretch 和 align-items: stretch,所有子元素都会自动填满单元格。这个默认行为在绝大多数场景下都合理,但做按钮、单选卡片、图标这类元素时会显得很奇怪。比如 Grid 里放一个按钮,按钮会被横向拉伸,宽度撑满整个单元格。
解决方式是在子元素上用 place-self: center:
css复制.grid .button {
place-self: center;
}
place-self 是 justify-self 和 align-self 的合并属性,直接在子元素上覆盖默认拉伸即可。
4.4 高度不对:一行有多高取决于它里面最高的那个子元素
Grid 里每行的高度是由最大内容决定的。如果一行里有图片有文字,图片的原始高度是 800px,这一行就会变成 800px,即便文字只有两行。想解决问题,可以用 grid-auto-rows 固定高度,再结合 align-items: start 让内容从顶部对齐:
css复制.grid {
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 200px;
align-items: start;
}
.grid .item {
overflow: auto;
}
注意固定行高之后,内容一旦超过 200px 会被 overflow: auto 截断滚动,模版渲染时要注意数据长度是否可控。
4.5 隐式网格导致的布局错位
当你往 Grid 里塞的子元素数量超过了模板定义的轨道数量,浏览器会自动生成隐式轨道。隐式轨道默认宽度为 auto,高度为 auto,很容易出现"没事多了一行"或者"多出来一行列宽不对"的情况。比如 grid-template-columns: 1fr 1fr 定义了 2 列,突然出现 3 个子元素,第 3 个会落到第二行,但行高是 auto,和第一行高度可能不匹配。
控制隐式网格大小时,用 grid-auto-columns 和 grid-auto-rows:
css复制.grid {
grid-template-columns: repeat(2, 1fr);
grid-auto-rows: minmax(100px, auto);
}
这样即使出现多余项,行高也被统一约束,不至于乱。排查这类错位问题,最有效的手段是打开浏览器 DevTools 检查元素,Grid 布局会显示所有轨道线的 overlay,一眼就能看出哪些元素掉进了隐式轨道。
4.6 兼容性:项目上线前必须检查的目标浏览器
Grid 布局在现代浏览器里非常稳定,包括 Chrome 88+、Firefox 89+、Safari 14.1+ 都完全支持 fr、gap、auto-fit 这些特性。但如果你要做企业后台这种对老浏览器有要求的环境,建议先查一份兼容性数据,再决定是否添加 @supports 降级方案。我一般做法是:
css复制.container {
display: flex;
flex-wrap: wrap;
}
@supports (display: grid) {
.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
}
先安排一个 flex 的兜底布局,支持 Grid 的浏览器自动走 Grid,不支持的就用 flex-wrap 凑合,保证功能可用再谈样式。
5. Grid 中容易被忽略的进阶用法
这里不是炫技,而是真实项目里经常会遇到、但很多教程没提的东西。我认为它们比冷门属性更值得掌握。
5.1 grid-auto-flow: dense 填坑
在处理动态数据渲染时,Grid 子项如果长短不一,默认流式排列会在中间留白。例如一个 2 列网格,第 1 项很高占了整行,第 2 项很短,第 3 项原本排在下一行左列,结果因为第 1 项占了一整行,第 3 项被迫挪到下行,左边空出一个格子。
设置 grid-auto-flow: dense 之后,浏览器会反.search 把短项塞回前面的空位:
css复制.grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-auto-flow: dense;
gap: 12px;
}
注意 dense 是"尽可能密集"的意思,元素的 DOM 顺序会与视觉顺序不同,如果视觉顺序有严格方向性要求(比如时间线、导航流),不要用 dense,否则容易造成阅读混乱。
5.2 Grid 和 flex 的嵌套是常态,不是替代关系
我见过很多初学者纠结一个问题:到底该用 Grid 还是 flex?我的回答是:整体页面骨架用 Grid,单元格内部再放 flex,两者组合才是真正的生产效率。
比如卡片区域用 Grid 排成多列,每张卡片内部用 flex 垂直排列文字、按钮;菜单栏用 Grid 控制外层列数,菜单项内部再用 flex 控制图标和文字对齐。Grid 解决的是"区域规划"问题,flex 解决的是"模块内部对齐"问题,二者配合才能写出既简洁又灵活的布局。
5.3 用 Grid 做拖拽面板的雏形
后台项目里常见的可拖拽仪表盘,核心其实是 grid-template-areas 加响应式重新排列。只要你把面板的 grid-area 定义好,拖拽后只需要改变项目对应的 grid-area 名称字符串,就能实现面板位置的重新排布。这个做法比操作绝对定位的 left 和 top 省心得多,也更容易做动画。
javascript复制function swapArea(areaName, newNodeValue) {
const container = document.getElementById('dashboard');
container.style.gridTemplateAreas = newNodeValue;
}
虽然拖拽逻辑那一层还是要自己写,但布局层用 Grid 来做,数据驱动视图的思路会特别清晰。
写在最后
说到底,CSS Grid 不是什么高深莫测的技术,它就是一套更适合做二维布局的工具。我从 flex 迁移到 Grid,最大的感受不是学会了多少个新属性,而是我拿到一个页面设计稿的时候,会先问自己:这到底是一张表格还是一排珠子?想清楚这个问题,代码自然就有了方向。
我个人现在的习惯是:后台框架一律 Grid,卡片网格一律 Grid,需要真正纵向横向都能控制对齐的一律 Grid;普通导航、按钮组、文字行内排列以及 flex 更直观的窄场景,仍然用 flex。没有哪个布局方式需要你从一而终,CSS 本来就允许我们混合搭配,选最顺手、最容易维护的方案即可。
如果你刚开始学 Grid,我建议你别去背那些复杂的网格线移动写法,先把 grid-template-columns、gap、fr、place-items 这四个东西反复用熟,再去碰 grid-template-areas 和 span。等你能用 Grid 把圣杯布局和卡片墙写出来,再回头看以前那些 flex 嵌套三层以上的代码,你会和我当初一样,忍不住发出"怎么没早点换"的感慨。
