做前端这些年,页面布局始终是绕不开的核心话题。CSS Grid 这套二维布局系统推出之后,我基本告别了用浮动拼版、用负 margin 硬拉间距的“考古式开发”。如果你只想记住一套布局方案,那我会直接推荐 Grid。这篇文章把我平时真正高频使用的 Grid 布局模板、核心属性、响应式写法以及踩过的坑都整理出来,配合可直接复制的代码,新手看完能直接用,老手也能查漏补缺。
1. 为什么说 Grid 是布局体系的“关键拼图”
1.1 Flex 与 Grid 的分工:一维与二维的区别
很多人刚接触 Grid 时会问:Flex 不是也很好用吗?两者确实都是现代布局的主力,但适用场景明显不同。Flex 的设计核心是一维布局,它只处理“主轴”方向上的排列和换行,虽然 flex-wrap 可以折行,但折行后的对齐关系依然不完整。Grid 的定位则是二维布局,它直接操作行和列两个轴,所有子项同时排列在行列交叉形成的单元格里。
举个直观的例子:你要做一张四列三行的卡片墙,用 Flex 可以在水平方向排卡片,但想精确控制“第一行第二列”“第三行第一列”这类位置,就得靠 order 或计算宽度来绕。Grid 一句 grid-template-columns: repeat(4, 1fr) 加 grid-template-rows,行列关系立刻清晰。所以在正式项目里,纵向导航、单条按钮组、居中分布这类一维场景我优先选 Flex;只要涉及“表格状”的二维区域、页面骨架、栅格卡片,直接 Grid。
1.2 脱离文档流噩梦:Grid 让“行列对齐”变自然
早期的浮动布局依赖 float: left 和 clearfix,一行排 4 个盒子要算宽度、防换行、防父容器高度塌陷。Grid 出现之后,子项默认撑满所在的网格单元格,不再需要手写清除浮动,也不必担心某个子项高度不一样把整行拖歪。因为行高由 grid-template-rows 决定,项的内容再多,行高也按最大内容扩展,整行的底边始终对齐。
这种设计带来的最大好处是“结构即视觉”。页面块之间的间距用 gap 控制,不需要给每个子项单独设 margin,后面想改间距只动一个属性。我去维护老项目时,经常看到十几个 .mb-20 这类间距 class,Grid 新代码里基本可以消灭掉这种冗余。对团队协作来说,布局规则集中在父容器里声明,新成员看代码的成本也低得多。
1.3 Grid 的跨浏览器现状:放心用的前提
曾经制约 Grid 普及的是浏览器兼容问题,但现在的现代浏览器对 Grid 的支持已经很稳定。即便在移动端 WebView 里,只要 Android 系统版本不是太老,display: grid 基本都能正常工作。写代码时我会关注两点:一是 gap 的兼容性,旧浏览器里 grid-gap 是带前缀的写法,需要适当处理;二是 auto-fit 和 minmax() 搭配的响应式行为,不同浏览器对“隐式轨道”的解析略有差异,但整体不影响主流程。
如果团队还需要支持 IE11,Grid 会有些麻烦,因为当年 IE 实现的是旧版规范,属性名和现代 Grid 不同。我的建议是:面向存量老系统时老老实实用 Flex,面向新项目直接用 Grid。现代开发环境下,没必要因为 1% 的旧用户放弃整个布局体系。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 上手必会的 Grid 核心概念与属性速查
2.1 容器属性:从 display:grid 到 grid-template-areas
先用最简单的方式启动 Grid:
css复制.grid-container {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: auto auto;
gap: 16px;
}
只要给父容器设了 display: grid,所有直接子项就变成 grid item,这会改变它们的 display 计算值,所以不必再逐个给子项设置浮动或者 display: inline-block,直接按网格排列。
grid-template-columns 定义列结构,上面代码表示第一列 200px,第二列占据剩余空间。grid-template-rows 定义行结构,auto 表示按内容自适应高度。gap 是行和列之间的间距,也可以用 row-gap、column-gap 单独控制。
如果列很多,推荐 repeat():
css复制grid-template-columns: repeat(12, 1fr);
这代表 12 列等宽。做复杂页面骨架时,还可以用 grid-template-areas 实现区域命名:
css复制.app-layout {
display: grid;
grid-template-columns: 220px 1fr;
grid-template-rows: 64px 1fr auto;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
height: 100vh;
}
配合子项的 grid-area:
css复制.header {
grid-area: header;
}
.sidebar {
grid-area: sidebar;
}
.main {
grid-area: main;
}
.footer {
grid-area: footer;
}
这种写法最大的价值是页面结构一眼可读,哪里是头部、哪里是侧栏、哪里是主内容,全在 CSS 地图里标注出来了。
2.2 项目属性:grid-column、grid-row 与 span 的配合
Grid 容器里的每个子项都可以用 grid-column 和 grid-row 精确描述占位。它们接收的是网格线的编号,注意是从 1 开始的,不是 0。
css复制.item {
grid-column: 1 / 3; /* 从第1条列线到第3条列线,表示占两列 */
grid-row: 1 / 2; /* 从第1条行线到第2条行线,表示占一行 */
}
如果不想写结束线,可以直接用 span:
css复制.item {
grid-column: 1 / span 2;
grid-row: 1 / span 1;
}
span 2 表示跨越 2 个轨道。很多做仪表盘的同学喜欢把某个“大图表”单独拉宽,比如下面的规则可以让某个卡片跨整行:
css复制.card-wide {
grid-column: 1 / -1;
}
-1 是最后一条网格线,所以 1 / -1 表示从最左边跨到最右边,完美实现通栏卡片。记住这个技巧,做报表页特别省事。
2.3 单位 fr 和 repeat() 到底怎么理解
fr 是 Grid 里最灵活的单位,全称是 fraction,代表剩余空间的份数。理解它不能当成像素,而要当成“比例份”。
css复制grid-template-columns: 1fr 2fr;
这代表第二列是第一列的 2 倍宽。如果把 fr 和固定值混用:
css复制grid-template-columns: 200px 1fr 2fr;
浏览器会先分配固定 200px,再把剩余空间按 1:2 的比例分给后两列。这个特性在做“固定侧栏 + 自适应主区域”时非常实用。
repeat() 还能配合 minmax() 做出花活:
css复制grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
这行代码的意思是:每列最小 240px,最大 1fr,容器宽度够放几列就放几列,放不下就换行。这是做响应式卡片墙的核心写法,比写多个媒体查询省太多事。
为了方便回忆,我把常用容器属性和项目属性整理成一个速查表:
| 类别 | 属性 | 典型值 | 说明 |
|---|---|---|---|
| 容器 | display | grid / inline-grid | 启动网格 |
| 容器 | grid-template-columns | 200px 1fr / repeat(4, 1fr) | 定义列结构 |
| 容器 | grid-template-rows | auto / 100px 1fr auto | 定义行结构 |
| 容器 | grid-template-areas | "header header" 等 | 区域命名布局 |
| 容器 | gap | 16px / 20px 10px | 行列间距(可同时设两个值) |
| 容器 | justify-items | start / center / stretch | 子项在单元格内的水平对齐 |
| 容器 | align-items | start / center / stretch | 子项在单元格内的垂直对齐 |
| 子项 | grid-column | 1 / 3 或 1 / span 2 | 跨列控制 |
| 子项 | grid-row | 1 / 2 或 1 / span 1 | 跨行控制 |
| 子项 | grid-area | header / 1 / 1 / 3 / 3 | 区域名或四线定位 |
| 子项 | justify-self | stretch / center | 单项目水平对齐 |
| 子项 | align-self | stretch / center | 单项目垂直对齐 |
3. 五种高频 Grid 布局模板与完整代码
3.1 经典两栏布局:侧栏固定、主内容自适应
个人博客、后台管理、商品详情页都逃不过两栏布局。我常写的模板是:左侧 260px 固定侧栏,右侧主体占剩余宽度,底部如果放 footer 就通栏。
html复制<div class="two-column">
<aside class="sidebar">侧边栏</aside>
<main class="content">主内容区</main>
<footer class="footer">底部信息</footer>
</div>
css复制.two-column {
display: grid;
grid-template-columns: 260px 1fr;
gap: 24px;
}
.sidebar {
background: #f5f5f5;
padding: 16px;
}
.content {
background: #ffffff;
padding: 16px;
min-width: 0;
}
.footer {
grid-column: 1 / -1;
padding: 16px;
background: #eaeaea;
}
注意我给 .content 加了一个 min-width: 0,这是很容易被忽略的点。Grid 子项的默认 min-width: auto,如果主内容里放一张很宽的图片或很长的英文单词,横向就会把整个网格撑破。设了 min-width: 0 之后,主区域才会老老实实收缩到可用宽度内,内部再自行处理滚动或换行。
3.2 三等分与四等分卡片网格
卡片墙是 Grid 最舒服的应用场景。三等分直接:
css复制.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
四等分就把 repeat(3, 1fr) 改成 repeat(4, 1fr)。但真实业务往往不是固定列数,移动端要 1 列,平板要 2 列,桌面要 4 列,于是我用这套响应式写法:
css复制.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 20px;
}
一行能放几列完全由容器宽度决定。比如父容器宽 1200px,每个卡片最小 220px,间距 20px,这一行大约放 5 列;父容器宽 700px,就会自动变成 3 列。不需要写任何媒体查询,代码量直接减半。
我还习惯给卡片加一句 align-items: stretch,默认值也是这个,它能保证同一行卡片高度一致。如果某些卡片内容特别少,也不想被拉高,可以改成 align-items: start,让卡片按自身内容高度排列,但那样整体视觉对齐会差一些。实践下来,多数后台页面用 stretch 更整齐。
3.3 圣杯式布局:header + 双栏/三栏 + footer
圣杯布局指的是“头部 + 两栏/三栏 + 底部”的模式,Grid 实现起来比传统负 margin 方案干净得多。
html复制<div class="holy-grail">
<header class="hg-header">顶部</header>
<aside class="hg-left">左侧</aside>
<main class="hg-main">中间主内容</main>
<aside class="hg-right">右侧</aside>
<footer class="hg-footer">底部</footer>
</div>
css复制.holy-grail {
display: grid;
grid-template-columns: 200px 1fr 200px;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header"
"left main right"
"footer footer footer";
height: 100vh;
}
.hg-header {
grid-area: header;
}
.hg-left {
grid-area: left;
}
.hg-main {
grid-area: main;
}
.hg-right {
grid-area: right;
}
.hg-footer {
grid-area: footer;
}
这套结构里 grid-template-rows: auto 1fr auto 是经典的后台框架写法:头部和底部按内容高度,中间区域撑满剩余高度。用 100vh 让整个页面占满视口,中间主内容区域出现滚动条时,侧边栏和头部保持固定。这种“框架感”用 Grid 一次成型,比以往用 position: fixed 硬顶要可靠很多。
3.4 整页骨架屏:grid-template-areas 实现“头、侧、主、脚”
管理后台最常见的整页骨架是顶部导航、左侧菜单、右侧内容、底部状态栏。用区域命名方案可以实现一套非常整的框架:
css复制.admin-page {
display: grid;
grid-template-columns: 240px 1fr;
grid-template-rows: 56px 1fr 36px;
grid-template-areas:
"navbar navbar"
"sidebar main"
"footer footer";
height: 100vh;
}
然后把各个区块的 grid-area 对应上,导航栏横跨顶部,左侧菜单固定宽度,主内容自适应,底部状态栏横跨全景。这种写法的优点是“配置文件式”地管理区域,想调整结构,比如把侧栏换到右边,只需要调整 grid-template-columns 和 grid-template-areas 的顺序,DOM 顺序甚至都不用动。
需要注意一点:grid-template-areas 里的字符串行数必须和 grid-template-rows 的行数对应,列数必须和 grid-template-columns 的列数对应。我经常在写的时候少写一列,结果区域名对不上,整个网格就会乱。刚上手时建议用“点”表示空白占位,比如:
css复制grid-template-areas:
"header header"
"sidebar main"
"footer footer";
每行两个名称,对应两列。如果某些单元格不需要内容,可以写成:
css复制grid-template-areas:
"header header"
"sidebar .";
点号表示空单元格,也不影响整体布局。
3.5 混合 Flex 属性让 Grid 更灵活
Grid 和 Flex 不是二选一的关系,实际项目里我经常在 Grid 容器内部再用 Flex 做细节。比如卡片网格中,每张卡片的内容头部要“图标在左、操作按钮在右”,这时卡片内部就是一个典型的 Flex 横向布局:
css复制.card-item {
display: flex;
align-items: center;
justify-content: space-between;
}
网格负责卡片在页面中的大位置,卡片内部 Flex 负责内容微排列。这样做的收益是各司其职:网格管二维骨架,Flex 管一维盒子。很多人写布局纠结选哪个,其实就是没有搞清楚这个分层逻辑。
4. 项目实战中的经验、陷阱与排查
4.1 最常踩的坑:隐式轨道与意外高宽
Grid 一旦行数量超出 grid-template-rows 定义的行数,超出部分会被放进“隐式轨道”。隐式轨道的尺寸默认是 auto,也就是内容决定高度,这经常引发滚动条跳动或高度崩塌。
我遇到过这样一个需求:卡片区域只有一行三列,但数据筛选后第 4 张卡片“漏”了出来,于是第二行以 auto 高度凭空出现,把 footer 挤到很下面。解决办法有两种:一种是明确用 grid-auto-rows 给隐式轨道定尺寸:
css复制.card-grid {
grid-auto-rows: minmax(120px, auto);
}
另一种是限制最大行数,比如指定 grid-template-rows: 1fr 并配合 overflow: hidden。个人推荐前者,因为隐式轨道是一种正常行为,很多时候我们确实不知道数据是否会多出一行,设置 grid-auto-rows 能让行高可控。
还有一个高频问题是 1fr 和 auto 的混淆。1fr 会先把确定尺寸的列分配完,再分剩余空间,所以它一定会占满父容器。auto 只会按内容宽度,内容不够宽时不会强行撑满。如果左右两栏里一边写 1fr 一边写 auto,右侧内容少就会显得很窄。理解这两个单位的差异能省去大量调试时间。
4.2 响应式布局的正确写法:repeat + minmax + auto-fit
我建议把下面这一行当作响应式网格的默认写法:
css复制.grid-responsive {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 16px;
}
用 auto-fill 还是 auto-fit,效果有一点差异。简单说,容器足够宽时两者视觉差不多;当容器较窄且列数不足时,auto-fill 会保留空轨道,而 auto-fit 会把空轨道收缩掉,让已有卡片铺满整行。实践下来,大多数数据卡片墙用 auto-fill,因为保留空轨道意味着列结构稳定;但如果你希望卡片在容器变宽时自动拉伸铺满,用 auto-fit 更合适。
还要配合一个 min-width: 0 的问题:minmax(260px, 1fr) 里的最小值是 260px,如果父容器宽度不足 260px,里面内容就会溢出或出现横向滚动。极端小屏幕下,需要再加一句:
css复制@media (max-width: 480px) {
.grid-responsive {
grid-template-columns: 1fr;
}
}
这样能保证手机端仍然是一列展示。
4.3 网格项重叠与 z-index 问题
Grid 允许不同子项占据同一个网格区域,这就带来了重叠的可能性。很多同学第一次遇到会说:“我明明只写了两个 item,为什么叠在一起了?”最常见的原因是 grid-template-areas 里把两个区域写进了同一个格子,或者某个 item 的 grid-column 范围覆盖了另一个 item。
重叠不总是坏事,比如做 Banner 时,背景图和一个提示文案叠在同一个区域是完全正常的需求。这时可以用 grid-area: 1 / 1 让多个 item 都定位到第一个单元格,再靠 z-index 控制层级:
css复制.banner-bg {
grid-area: 1 / 1;
z-index: 1;
}
.banner-text {
grid-area: 1 / 1;
z-index: 2;
}
但如果是无意重叠,排查顺序是:先看 grid-template-areas 是否重复命名,再看 grid-column 和 grid-row 的起止线,最后看有没有写成 grid-area: 1 / 1 / 3 / 3 这种四值写法时弄错了顺序。grid-area 的四值顺序是 row-start / column-start / row-end / column-end,不是常用的“列开始、列结束、行开始、行结束”,记反了就会得到完全不同的占位。这个顺序我踩过两次坑,现在写复杂定位时一定会注释标明起止线含义。
4.4 常见问题自查表
我把平时群里问得比较多的问题整理成一个速查表,方便遇到现象时直接对照:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 子项把网格撑破,出现横向滚动条 | 子项未设置 min-width: 0 或 min-height: 0 |
给对应 item 加上 min-width: 0 |
| 行数变多,底部被推出 | 隐式轨道未定义高度 | 加 grid-auto-rows: minmax(100px, auto) |
| 明明设置两列,却只有一列撑开 | 列宽用了 auto 或 max-content,未用 1fr |
需要自适应的列改成 1fr |
| 卡片高度参差不齐 | 容器缺少对齐设置 | 默认 align-items: stretch,或显式声明 |
| 换行时元素位置错乱 | grid-auto-flow 策略不符合预期 |
显式设置 grid-auto-flow: row 或 column |
| 区域命名的部分格子空白 | grid-template-areas 行列数与模板不匹配 |
使用 . 占位并核对行列数 |
| 间隙不生效 | 属性名写成了 grid-gap-* 旧语法 |
使用标准 gap / row-gap / column-gap |
4.5 与 Web 渲染相关的小技巧和扩展方向
Grid 虽然主打布局,但和动画、交互结合时也有不少便利。比如在做倒计时数字切换时,可以把多个数字堆叠在同一个 Grid 单元格里,通过 transform 切换位置,这种“数字滚动”效果比逐字排版更容易实现。具体思路是:
css复制.digit {
display: grid;
grid-template-areas: "stage";
}
.digit-item {
grid-area: stage;
transition: transform 0.3s ease;
}
所有数字占同一个区域,切换时用 translateY 就能做出翻牌效果。另外,热门搜索里的“涟漪光圈扩散”“流光边框”也经常和 Grid 配合:外层的 grid 负责定位,内层的伪元素动画负责视觉扩散。Grid 不只是一个静态排版工具,它提供的“区域叠加能力”为很多视觉特效提供了稳定的坐标基础。
实际开发时我还建议把常用的 Grid 布局封装成工具类,比如:
css复制.grid-cols-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
.grid-cols-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.grid-cols-auto { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; }
这样团队里其他人写页面时直接复用,避免每个人写出不同心意的 gap 和列数。当然,项目里如果已经有 Tailwind 这类原子化 CSS,也可以直接用它的 grid-cols-* 工具类,原理是一样的。
最后再分享一个小技巧:调试 Grid 布局时,打开浏览器开发者工具,选中网格容器,Elements 面板里会显示网格线覆盖层,行列编号和区域名都能直接可视化。不要光靠肉眼猜,先看网格线,再看子项的 grid-column / grid-row,绝大多数布局问题都能当场定位。这也是我近几年使用 Grid 以来最想推荐给所有人的习惯。
