CSS Grid实战技巧

张玥 2026年9月18日 阅读时间 50分钟
CSS Grid 网格布局 响应式 auto-fit subgrid 实战
热门CSS技巧之CSS Grid实战技巧

如果只能推荐一个现代 CSS 布局工具,答案毫无悬念是 Grid。它把过去需要浮动、负边距、表格布局、JavaScript 计算才能勉强实现的版式,压缩成了几行声明式代码。display: grid 一出现,页面布局从“把盒子推到它该在的地方”变成了“先画一张地图,再让元素住进去”。然而真正把 Grid 用好的人并不多——大多数人只停留在 grid-template-columns: repeat(3, 1fr) 这一句上,遇到不规则排版、跨行跨列、响应式重排、圣杯布局、仪表盘,还是回到 Flexbox 甚至绝对定位。这篇 50 分钟的长文,会从心智模型讲到实战配方,配有六个可以当场动手的交互实验台,帮你把 Grid 从“会用”推到“用熟、用巧、用到别人看不出是 Grid 做的”。

一、为什么要学 Grid:一维已死,二维当立

在 Grid 之前,CSS 布局的历史大致经历了三个阶段:

  • 表格布局时代:用 <table> 排版,语义混乱,响应式几乎不可能。
  • 浮动与定位时代:float + clear + 负 margin,需要清除浮动、需要计算宽度、需要“最后一列去 margin”这种补丁。
  • Flexbox 时代:解决了单行或单列的弹性分配,但依然是一维的——它只能沿主轴排列,交叉轴上的对齐能力有限。

Grid 的定位非常明确:它是一个二维布局系统。你同时定义行和列,然后把元素放进由行列交叉形成的单元格里。Flexbox 关心的是“这一排元素怎么分配空间”,Grid 关心的是“这块区域如何被切分成网格”。两者不是替代关系,而是分工关系。

/* 用 Flexbox 做 3 列卡片,需要处理换行与间隙 */
.cards {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}
.card {
  flex: 1 1 calc((100% - 32px) / 3);
  /* 还得担心最后一行只有 1~2 个卡片时被拉宽 */
}

/* 用 Grid 做同样的 3 列卡片 */
.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}
/* 最后一行不足 3 个时,卡片依然保持正确宽度 */
Flexbox 一维。适合导航、按钮组、单行排列、内容驱动的自适应
Grid 二维。适合整页骨架、卡片列表、仪表盘、不规则杂志排版
组合 Grid 搭骨架,Flexbox 做组件内部——这才是现代最佳实践
误区 不要问“谁更好”,要问“这个场景是几维的”

Grid 带来的三个观念转变

  1. 从“元素驱动”到“容器驱动”:过去先写元素再想办法排列,现在先在容器上定义网格,元素只是填进去而已。
  2. 从“计算宽度”到“声明比例”:再也不需要 calc((100% - 32px) / 3),一句 1fr 就够。
  3. 从“结构决定视觉”到“视觉与结构解耦”:用 grid-template-areas 可以让视觉顺序与 DOM 顺序分离,这在响应式重排时威力巨大。

二、拆解 Grid:五个必须记住的名词

Grid 的术语不多,但每一个都必须精确理解,否则后面所有的定位语法都会变成“背咒语”。

/* 1. 网格容器 Grid Container */
/* 声明 display: grid 的元素,它定义了整个网格 */
.container { display: grid; }

/* 2. 网格项目 Grid Item */
/* 容器的直接子元素,自动成为网格项目 */
/* 注意:只有直接子元素,孙元素不是 */

/* 3. 网格线 Grid Line */
/* 划分网格的线,从 1 开始编号,
3 列会有 4 条列线,2 行会有 3 条行线 */

/* 4. 网格轨道 Grid Track */
/* 两条相邻网格线之间的空间,即一行或一列 */

/* 5. 网格区域 Grid Area */
/* 由 4 条网格线围成的矩形区域,
可以跨多行多列 */

/* 一张图记住编号规则:*/
/* 列线 1 列线 2 列线 3 列线 4 */
/* | 列1 | 列2 | 列3 | */
/* 行线1 +-------+-------+-------+ */
/* | | | | */
/* 行线2 +-------+-------+-------+ */
/* | | | | */
/* 行线3 +-------+-------+-------+ */
1234
123
列1 行1
列2 行1
列3 行1
列1 行2
列2 行2
列3 行2

3 列 × 2 行 = 4 条列线 + 3 条行线

网格线编号可以倒数

这是 Grid 里最容易被忽略、却极其好用的特性:网格线可以从末尾倒数,用负数表示。-1 永远表示最后一条线,-2 表示倒数第二条。这意味着你不需要知道网格到底有几列,就能让元素横跨到最右边。

/* 让元素从第 1 条列线延伸到最右边,无论有多少列 */
.full-bleed {
  grid-column: 1 / -1;
}

/* 让元素占据最后两列 */
.last-two {
  grid-column: -3 / -1;
}

/* 从第 1 条行线一直延伸到最底部 */
.full-height {
  grid-row: 1 / -1;
}

三、从零开始:一个最小可用的 Grid

先把最简单的情况吃透。下面这段代码创建了一个 3 列 2 行的网格,六个子元素自动填充进去。

/* HTML */
<div class="grid">
  <div>1</div> ... 共 6 个
</div>

/* CSS:最小可用配置 */
.grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-template-rows: 80px 80px;
  gap: 16px;
}

/* 等价写法:用 repeat() 更简洁 */
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(2, 80px);
  gap: 16px;
}
3 列 × 2 行,gap: 16px
1
2
3
4
5
6

只有 display: grid 就够了

如果你只写 display: grid,什么都不会发生——所有子元素会挤成一列,每个元素占据一行。这是因为浏览器默认给了一个单列网格。你可以把它理解成:Grid 的默认行为是“垂直堆叠”,这恰好和块级元素的表现一致。

所以真正让 Grid 产生威力的是 grid-template-columns 和 grid-template-rows。只写列、不写行是最常见的写法——行会由内容自动撑开,这就是所谓的隐式行,我们后面会详细讲。

四、fr 单位:弹性布局的数学核心

fr 是 Grid 引入的新单位,全称 fraction(份)。它的语义非常直白:把剩余空间按份数分配。三个 1fr 就是三等分;1fr 2fr 1fr 就是 1:2:1 分配。

关键在于“剩余”两个字。如果网格里还有固定宽度的轨道或 auto 轨道,浏览器会先把它们算完,剩下的空间才交给 fr 去分。理解这一点,就能解释很多“为什么我的 fr 不按比例”的困惑。

1fr--
2fr--
1fr--

每个方块下方显示的是它实际计算出的像素宽度。拖动滑块,观察三列宽度如何按比例变化。

fr 与 auto 的区别

很多人会混淆 1fr 和 auto,它们的行为完全不同:

/* auto:先满足内容,再分配剩余空间 */
/* 内容宽的列会更宽,内容窄的列会更窄 */
.auto-cols {
  grid-template-columns: auto auto auto;
}

/* 1fr:忽略内容尺寸,强制等分剩余空间 */
/* 内容再长也只会撑破格子,不会改变列宽比例 */
.fr-cols {
  grid-template-columns: 1fr 1fr 1fr;
}

/* 重要:1fr 的隐含最小尺寸是 auto */
/* 也就是说,1fr 的列最小不会小于内容宽度 */
/* 结果:长内容会把列撑开,破坏等分效果 */

/* ✅ 修复:用 minmax(0, 1fr) 解除最小尺寸限制 */
.safe-cols {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* 这样即使内容里有长英文、长链接、pre 标签 */
/* 列宽依然严格等分,超出部分由 overflow 处理 */
常见翻车
grid-template-columns: 1fr 1fr 1fr;
第三列里放了一个超长的 URL,结果第三列被撑得特别宽,前两列被挤压。
正确写法
grid-template-columns: repeat(3, minmax(0, 1fr));
列宽严格等分,长内容在格子内部滚动或截断。

记住这个心法

1fr 不是“等分”,而是“分一份”;minmax(0, 1fr) 才是“严格等分”。只要你在项目里遇到过“网格被长内容撑爆”的问题,八成就是漏了 minmax(0, 1fr)。这是 Grid 实战中最实用的一条经验。

五、repeat() 与 minmax():把重复代码压成一行

Grid 提供了一整套“简写工具”,让复杂的网格定义变得短小精悍。掌握它们,代码量能减少一半以上。

/* repeat():重复轨道定义 */
grid-template-columns: 1fr 1fr 1fr 1fr;
/* 等价于 */
grid-template-columns: repeat(4, 1fr);

/* repeat() 可以混合固定值与弹性值 */
grid-template-columns: 200px repeat(3, 1fr) 200px;

/* minmax():定义轨道的最小与最大尺寸 */
grid-template-columns: repeat(3, minmax(200px, 1fr));
/* 每列最小 200px,最大可以分到 1fr 的空间 */

/* minmax() + auto:高度自适应但不超过内容 */
grid-template-rows: minmax(120px, auto);

/* 固定值与弹性值混用 */
grid-template-columns: 240px minmax(0, 1fr) 240px;
/* 典型的侧边栏 + 主内容 + 侧边栏三栏布局 */

/* 常用组合:自动填充 + 最小宽度 + 弹性最大宽度 */
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
/* 这一行几乎能解决 80% 的响应式卡片列表需求 */
固定轨道
200px

宽度恒定,容器再宽也不变

弹性轨道
1fr

按份数瓜分剩余空间

受限弹性
minmax()

既弹性又有上下限,最实用

内容自适应
auto

由内容决定尺寸

另一个冷门但好用的值:fit-content()

fit-content(300px) 的语义是:“内容需要多宽就多宽,但最多不超过 300px”。它和 minmax(auto, 300px) 类似,但在实际使用中更直观。适合用在标签栏、日期列、序号列这类“通常很窄,但偶尔会变宽”的场景。

/* 第一列宽度随内容变化,但最多 120px */
.table-like {
  display: grid;
  grid-template-columns: fit-content(120px) 1fr fit-content(100px);
  gap: 12px;
}

六、auto-fill 与 auto-fit:响应式卡片网格的真相

这一节的内容,是 Grid 在日常开发中出场率最高的场景:让卡片列表在不同屏幕宽度下自动调整列数,且不需要写任何媒体查询。

当前轨道数:--

两者的唯一区别

auto-fill 和 auto-fit 在计算轨道数量时行为完全一致,都是“能塞几列就塞几列”。区别只在最后一步:

  • auto-fill:如果容器还有剩余空间,创建空轨道占位。剩下的空间就空在那里。
  • auto-fit:把没有内容的空轨道折叠掉,让已有的轨道拉伸填满整行。

换句话说:当卡片数量足够多、能填满整行时,两者看起来完全一样;只有当最后一行的卡片数量不足以填满时,差异才会显现——auto-fill 会让卡片保持原宽度、右侧留白,auto-fit 会让卡片被拉宽、铺满整行。

/* 万能响应式卡片网格 */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 24px;
}

/* 这行代码的含义,展开来说是:*/
/* 1. 每一列最小 280px,最大不超过 1fr */
/* 2. 浏览器计算容器能放下几列 */
/* 3. 空隙被已有的列自动吸收 */
/* 4. 全程不需要媒体查询 */

/* 对比:用媒体查询的旧方案 */
.card-grid-old {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
}
@media (min-width: 640px) {
  .card-grid-old { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 960px) {
  .card-grid-old { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 1280px) {
  .card-grid-old { grid-template-columns: repeat(4, 1fr); }
}
卡片 A
卡片 B
卡片 C
卡片 D

容器变宽时列数自动增加,容器变窄时自动减少

该选 auto-fit 还是 auto-fill?

  • 选 auto-fit:卡片数量不固定、希望铺满整行、不希望出现右侧突兀留白。这是大多数卡片列表的选择。
  • 选 auto-fill:希望卡片尺寸保持稳定、不希望最后几张被拉得过宽。例如图标网格、缩略图墙、仪表盘上的小部件。

一个实用的判断技巧:如果你希望所有卡片宽度永远一致,用 auto-fill;如果你希望整行永远被填满,用 auto-fit。

七、gap 与对齐:把间距交给 Grid,把 margin 扔掉

在 Flexbox 和 Grid 出现之前,元素之间的间距几乎全靠 margin。而 margin 的副作用是:它会参与布局计算,会引发外边距折叠,还会让你不得不写 :last-child { margin-right: 0 } 这种补丁。

gap 彻底解决了这个问题:它只存在于轨道之间,不参与元素尺寸计算,不会折叠,也不需要处理首尾元素。

item 1
item 2
item 3
item 4
item 5
item 6

网格轨道是固定尺寸(88px × 62px),所以当 justify-items 不是 stretch 时,项目会缩到内容大小,对齐效果一目了然。

gap 的三种写法

/* 行列间距相同 */
.grid { gap: 16px; }

/* 两个值:行间距 列间距 */
.grid { gap: 16px 24px; }

/* 分开写 */
.grid {
  row-gap: 16px;
  column-gap: 24px;
}

/* 旧写法 grid-gap 已被 gap 取代,不再推荐 */

对齐属性的完整体系

Grid 的对齐属性很多,但它们有清晰的命名规则:前半个词决定方向,后半个词决定作用对象。

属性 作用对象 常用值
justify-items 所有项目在行内方向(水平)上的对齐 stretch / start / center / end
align-items 所有项目在块方向(垂直)上的对齐 stretch / start / center / end
justify-self 单个项目水平对齐(写在项目上) stretch / start / center / end
align-self 单个项目垂直对齐(写在项目上) stretch / start / center / end
justify-content 整个网格内容在容器中水平分布 start / center / space-between / space-around
align-content 整个网格内容在容器中垂直分布 start / center / space-between / space-around
place-items align-items + justify-items 简写 center / start / end
place-content align-content + justify-content 简写 center / start / space-between

记忆口诀:items 管项目,content 管整体,self 管自己;justify 是水平,align 是垂直。place-* 是前两者的简写,顺序是 align 在前、justify 在后。

一个高频需求:让某个项目单独居中

/* 整个网格居中对齐 */
.grid {
  display: grid;
  place-items: center;
}

/* 只有某一个项目居中 */
.grid .special {
  justify-self: center;
  align-self: end;
}

/* 经典:一个 div 里的元素水平垂直居中 */
.center-box {
  display: grid;
  place-items: center;
  min-height: 200px;
}

八、网格线定位:从“自动排列”到“精确落位”

前面所有例子里,元素都是自动填充的。而 Grid 真正强大的地方在于:你可以精确指定每个元素占据哪几条网格线。点击下面的任意单元格,看看它对应的定位代码。

/* 点击上方任意格子,查看它的网格线坐标 */

这是一个 4 列 × 3 行的网格。每个单元格都可以用 grid-column 与 grid-row 精确描述。

四种定位写法

/* 写法 1:起始线 / 结束线 */
.item {
  grid-column: 2 / 4;
  grid-row: 1 / 3;
}
/* 占据第 2~3 列、第 1~2 行 */

/* 写法 2:起始线 + span 跨越数 */
.item {
  grid-column: 2 / span 2;
  grid-row: 1 / span 2;
}
/* 效果与写法 1 完全相同,但更易读 */

/* 写法 3:只写起始线 */
.item {
  grid-column-start: 2;
  grid-column-end: 4;
}

/* 写法 4:负号从末尾倒数 */
.item {
  grid-column: 1 / -1;
}
/* 横跨整个网格,无论多少列 */
跨列 grid-column: 1 / 3 或 1 / span 2
跨行 grid-row: 2 / 4 或 2 / span 2
全宽 grid-column: 1 / -1 最省心
注意 数字是线的编号,不是格子的编号

span 的两种写法差异

grid-column: 2 / span 2 与 grid-column: span 2 / 4 都能实现“跨两列”,但语义不同:

  • 2 / span 2:从第 2 条线开始,往后跨 2 条。起点固定,终点由跨数决定。
  • span 2 / 4:在第 4 条线结束,往前跨 2 条。终点固定,起点由跨数决定。

在自动布局的场景里,后者往往更稳妥,因为它保证了元素右侧的边界固定,不会因为前面元素的位置变化而整体漂移。

一个立竿见影的例子:图片网格

/* 4 列图片网格,第一张图跨 2×2 */
.gallery {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 120px;
  gap: 12px;
}
.gallery > :first-child {
  grid-column: span 2;
  grid-row: span 2;
}
/* 其余图片自动填充到剩下的格子里 */

九、grid-area 与命名区域:把布局画成一张地图

如果只能选一个 Grid 特性来展示它的“爽感”,我会选 grid-template-areas。它允许你用可视化的字符串直接描述页面结构,一眼就能看懂布局长什么样。

同一套 HTML,只换一份 grid-template-areas,就能得到完全不同的版式。

语法规则

/* 每一行字符串代表一行网格,
同一行内用空格分隔每一列 */
.layout {
  display: grid;
  grid-template-columns: 200px 1fr 200px;
  grid-template-areas:
    "header header header"
    "sidebar main aside"
    "footer footer footer";
}

/* 元素通过 grid-area 认领自己的区域 */
.layout > header { grid-area: header; }
.layout > .sidebar { grid-area: sidebar; }
.layout > main { grid-area: main; }
.layout > aside { grid-area: aside; }
.layout > footer { grid-area: footer; }

/* 用点号表示空白区域 */
.layout-dots {
  grid-template-areas:
    "header header ."
    "sidebar main main";
}

/* 连续的点号可以简写成一个 */
/* "..." 与 ". . ." 等价 */
可视化 代码本身就是布局图,评审时一眼看懂
响应式利器 媒体查询里重写 areas 就能完成整体重排
顺序解耦 视觉顺序与 DOM 顺序分离,利于 SEO 与无障碍
注意 所有行必须列数相同,否则整条声明失效

响应式重排:一行媒体查询搞定

这是 grid-template-areas 最有价值的用法。在移动端把三栏改成单栏,只需要重写 areas 和 columns,不需要动任何 DOM。

/* 桌面端:三栏 */
.layout {
  display: grid;
  grid-template-columns: 200px 1fr 200px;
  grid-template-areas:
    "header header header"
    "sidebar main aside"
    "footer footer footer";
}

/* 移动端:单栏,侧边栏挪到主内容下方 */
@media (max-width: 768px) {
  .layout {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "main"
      "aside"
      "sidebar"
      "footer";
  }
}

注意这里发生了什么:视觉顺序变了,但 DOM 顺序完全没变。屏幕阅读器依然按照 header → sidebar → main → aside → footer 的顺序朗读,搜索引擎也依然按原结构解析。这就是 Grid 在无障碍方面胜过“用绝对定位强行重排”的根本原因。

十、隐式网格与 grid-auto-flow:当元素溢出了

当你只定义了列、没有定义行时,元素会自动创建新行——这些新行叫做隐式行。它们的高度由 grid-auto-rows 决定,默认为 auto(由内容撑开)。

/* 只定义列,行自动生成 */
.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}

/* 控制隐式行的高度 */
.grid {
  grid-auto-rows: 120px;
}

/* 或使用 minmax 让行高有弹性 */
.grid {
  grid-auto-rows: minmax(100px, auto);
}

/* 控制隐式列的宽度 */
.grid {
  grid-auto-columns: 1fr;
}

/* grid-auto-flow:控制自动填充的方向 */
grid-auto-flow: row;    /* 默认,逐行填充 */
grid-auto-flow: column; /* 逐列填充 */
grid-auto-flow: row dense; /* 逐行填充 + 填补空洞 */
grid-auto-flow: column dense;
row 默认。先填满一行,再填下一行
column 先填满一列,再填下一列
dense “紧凑”模式,回头填补前面留下的空洞
代价 dense 会打乱视觉顺序,慎用于有语义的内容

dense 的典型场景

想象一个图片墙,大部分图片占 1×1,少数图片占 2×2。普通模式下,大图后面的位置会留空;开启 dense 后,小图会自动回填到空洞里,整个网格变得紧凑。

/* 图片墙:允许小图回填空洞 */
.masonry-like {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  grid-auto-rows: 100px;
  grid-auto-flow: row dense;
  gap: 10px;
}
.masonry-like .wide { grid-column: span 2; }
.masonry-like .tall { grid-row: span 2; }
.masonry-like .big  { grid-column: span 2; grid-row: span 2; }

/* 重要提醒:dense 会改变视觉顺序 */
/* 如果内容有阅读顺序要求,请谨慎使用 */

十一、重叠与层叠:Grid 天生支持 Z 轴

Grid 有一个其他布局方式都做不到的能力:多个元素可以占据同一个网格单元格。它们会互相重叠,通过 z-index 控制前后顺序。这在做图文叠加、封面标题、图片说明时非常方便。

/* 同一个单元格里放两个元素 */
.hero {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
}
.hero > * {
  grid-area: 1 / 1 / 2 / 2; /* 全部重叠 */
}
.hero .text {
  z-index: 1;
  align-self: end;
  padding: 24px;
  color: white;
  background: linear-gradient(transparent, rgba(0,0,0,0.7));
}

/* 图片铺满容器 */
.hero img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
A 2×2
B 2×2
C
D

A 与 B 重叠,B 通过 z-index 叠在 A 上方

经典应用:卡片上的浮动标签

/* 卡片右下角叠加一个角标 */
.product {
  display: grid;
  grid-template-areas: "cover";
  border-radius: 12px;
  overflow: hidden;
}
.product > img   { grid-area: cover; }
.product > .badge {
  grid-area: cover;
  justify-self: end;
  align-self: start;
  margin: 12px;
  padding: 4px 10px;
  background: #ef4444;
  color: white;
  border-radius: 10px;
  font-size: 0.72rem;
}

这套写法的优雅之处在于:不需要 position: absolute,不需要给父元素加 position: relative,角标自动叠在图片上,还能用 justify-self / align-self 精确控制它出现在四个角落中的哪一个。

十二、subgrid:让子网格继承父网格轨道

你有没有遇到过这种情况:一个卡片列表,每张卡片内部有自己的标题、正文、按钮。你希望所有卡片的标题在垂直方向上严格对齐,但每张卡片的高度由内容决定,很难做到。

subgrid 就是为了解决这类问题而生的。它允许子元素直接使用父网格的轨道,从而实现跨卡片的对齐。

/* 父网格:每张卡片占一行,内部 3 行 */
.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}

/* 子网格:卡片内部的行由父级统一定义 */
.card {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
}

/* 结果:所有卡片的标题行、正文行、按钮行
在垂直方向上严格对齐,无论内容多长 */

/* 列方向也可以继承 */
.form-row {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
}

/* 注意:subgrid 需要元素本身是网格项目 */
/* 也就是说,必须先有 display: grid */
grid-template-rows: subgrid 继承父级的行轨道
grid-template-columns: subgrid 继承父级的列轨道
典型场景 卡片内标题/正文/按钮对齐、表单跨列对齐
兼容性 Chrome 117+ / Firefox 71+ / Safari 16+

没有 subgrid 时的替代方案

在 subgrid 普及之前,社区常用两种“土办法”实现卡片内对齐:

  1. 固定行高:给卡片的标题、正文、按钮分别设置固定高度。缺点是内容长度不可控,容易溢出或留白。
  2. Flexbox + margin-top: auto:把卡片设为纵向 Flex,按钮用 margin-top: auto 推到底部。这能解决“按钮贴底”,但解决不了标题跨卡片对齐。

所以如果目标用户主要使用现代浏览器,subgrid 是更彻底的方案。使用前建议先确认项目的浏览器支持范围。

十三、实战一:响应式卡片网格

把前面的知识点串起来,做一个最常用、也最容易出错的东西:卡片列表。

/* 基础版:自动响应,无媒体查询 */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 24px;
}

/* 注意 min(280px, 100%) 这个技巧:*/
/* 当容器宽度小于 280px 时,最小值退化为 100% */
/* 避免小屏出现横向溢出 */

/* 进阶版:控制每行最多 4 列 */
.card-grid-limited {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  max-width: 1280px;
  margin-inline: auto;
  gap: 24px;
}

/* 卡片内部:纵向 Flex,按钮贴底 */
.card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 20px;
  background: white;
  border-radius: 12px;
  contain: layout paint;
}
.card .actions {
  margin-top: auto;
}

/* 长文本防溢出 */
.card h3 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
拖动浏览器窗口,观察列数变化
设计系统
组件库
性能优化
无障碍
工程化

三个必须注意的细节

  1. 用 min(280px, 100%) 而不是直接写 280px:否则在窄于 280px 的容器里,minmax 的最小值超过容器宽度,会导致横向滚动条。
  2. 卡片内部用 Flexbox:Grid 负责排列卡片,Flexbox 负责卡片内部。不要让一个 Grid 承担所有事情。
  3. 加 contain: layout paint:卡片数量多时,它能显著减少重绘范围,滚动更顺滑。

十四、实战二:整页骨架与圣杯布局

圣杯布局(Holy Grail Layout)是前端面试的经典题,也是 Grid 最能展示威力的场景。用 Flexbox 实现需要 order、负 margin、calc() 一堆技巧;用 Grid 只需要几行。

/* 圣杯布局:头 / 尾通栏,中间三栏 */
.holy-grail {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr) 200px;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header header"
    "nav main aside"
    "footer footer footer";
  min-height: 100vh;
  gap: 0;
}

/* 关键:1fr 行让主内容区撑满剩余高度 */
/* 页脚永远贴在视口底部,无需任何 JS */

/* 各区域认领位置 */
.holy-grail > header { grid-area: header; }
.holy-grail > nav    { grid-area: nav; }
.holy-grail > main   { grid-area: main; }
.holy-grail > aside  { grid-area: aside; }
.holy-grail > footer { grid-area: footer; }

/* 移动端重排:单栏堆叠 */
@media (max-width: 768px) {
  .holy-grail {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "main"
      "nav"
      "aside"
      "footer";
  }
}
header
sidebar
main
aside
footer

grid-template-rows: auto 1fr auto 让主内容自动撑满高度

为什么用 minmax(0, 1fr) 而不是 1fr

在中间主内容列上,这个细节至关重要。主内容区经常包含 <pre>、长表格、代码块,如果写 1fr,它会因为“1fr 的最小值是 auto”而被撑开,进而把整个页面撑出横向滚动条。写成 minmax(0, 1fr) 后,主内容列严格遵循 1fr 的分配,超出的内容由内部的 overflow: auto 处理。

/* 完整的页面骨架配方 */
body {
  display: grid;
  grid-template-rows: auto 1fr auto;
  min-height: 100vh;
  margin: 0;
}

/* 主内容区允许内部滚动 */
main {
  overflow: auto;
  min-width: 0;
}

十五、实战三:后台仪表盘

后台管理系统是最能体现 Grid 价值的场景之一:顶部导航、侧边菜单、多个数据卡片、图表区、表格区,模块多且尺寸不一。用 grid-template-areas 描述,整个页面结构一目了然。

/* 仪表盘布局 */
.dashboard {
  display: grid;
  grid-template-columns: 240px repeat(4, minmax(0, 1fr));
  grid-template-rows: 64px auto auto minmax(0, 1fr);
  grid-template-areas:
    "topbar topbar topbar topbar topbar"
    "side kpi1 kpi2 kpi3 kpi4"
    "side chart chart chart list"
    "side table table table list";
  gap: 16px;
  padding: 16px;
  min-height: 100vh;
}

/* 侧边栏跨越 3 行,天然实现“固定侧边 + 滚动主区” */
/* 无需 position: fixed,也就不会有内容被遮挡的问题 */
topbar
side
kpi1
kpi2
kpi3
chart
list
table

仪表盘的响应式退化策略

后台系统通常以桌面为主,但也要考虑小屏。推荐的做法是分档处理:

  • ≥ 1200px:完整五列布局,侧边栏常驻。
  • 768px ~ 1200px:侧边栏收成图标条(80px),KPI 卡片从 4 列变 2 列。
  • < 768px:侧边栏变成抽屉或底部 Tab,所有内容单列堆叠。
@media (max-width: 1200px) {
  .dashboard {
    grid-template-columns: 80px repeat(2, minmax(0, 1fr));
    grid-template-areas:
      "topbar topbar topbar"
      "side kpi1 kpi2"
      "side kpi3 kpi4"
      "side chart chart"
      "side table table";
  }
}

@media (max-width: 768px) {
  .dashboard {
    grid-template-columns: 1fr;
    grid-template-areas:
      "topbar"
      "kpi1"
      "kpi2"
      "kpi3"
      "kpi4"
      "chart"
      "table"
      "side";
  }
}

十六、实战四:杂志式不规则排版

杂志风格排版的特点是:元素大小不一、跨越行列、错落有致。这种版式如果用绝对定位实现,几乎无法响应式;而用 Grid,只要定义好网格骨架,剩下的就是让元素“认领”不同大小的区域。

/* 杂志式首页骨架 */
.magazine {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  grid-auto-rows: minmax(120px, auto);
  gap: 20px;
}

/* 头条:横跨全部 6 列 */
.magazine .hero {
  grid-column: 1 / -1;
  grid-row: span 2;
}

/* 主特稿:左侧 4 列,纵跨 3 行 */
.magazine .feature {
  grid-column: 1 / 5;
  grid-row: span 3;
}

/* 右侧边栏:占 2 列,纵跨 3 行 */
.magazine .side {
  grid-column: 5 / 7;
  grid-row: span 3;
}

/* 底部三张小编排:自动分配 */
.magazine .mini {
  grid-column: span 2;
}

/* 移动端:全部单列 */
@media (max-width: 768px) {
  .magazine {
    grid-template-columns: 1fr;
  }
  .magazine > * {
    grid-column: 1 / -1 !important;
    grid-row: auto !important;
  }
}
hero 1 / -1
feature 1 / 3
2 行
3 行
3 行

同一套网格骨架,通过不同的 span 组合出丰富版式

一个实用技巧:用 grid-auto-rows 控制节奏

杂志排版里,行高的选择至关重要。如果写 grid-auto-rows: 1fr,所有行会平均分配高度,内容少的行会显得空旷;如果写 auto,行高完全由内容决定,跨行元素的高度就难以预期。

最稳妥的做法是 grid-auto-rows: minmax(120px, auto):给一个最小高度保证节奏感,同时允许内容撑开。这样即使某个区块的内容特别多,也不会把整个版式挤垮。

十七、实战五:表单与标签对齐

表单是最容易被忽视的 Grid 应用场景。用 Grid 做表单,可以天然实现标签与输入框的对齐,并且支持“某些字段占满整行”。

/* 两列表单:左标签,右输入 */
.form {
  display: grid;
  grid-template-columns: 120px minmax(0, 1fr);
  gap: 16px 20px;
  align-items: center;
}

/* 标签右对齐,与输入框贴合 */
.form > label {
  text-align: right;
  color: #64748b;
}

/* 需要占满整行的字段 */
.form > .full {
  grid-column: 1 / -1;
}

/* 让输入框内部对齐(多行文本) */
.form > textarea {
  align-self: start;
}

/* 小屏:标签改为顶部 */
@media (max-width: 600px) {
  .form {
    grid-template-columns: 1fr;
    gap: 6px;
  }
  .form > label {
    text-align: left;
  }
  .form > .full {
    grid-column: auto;
  }
}
请输入用户名
name@example.com
+86
备注(占满整行)

为什么不用 display: table 或 Flexbox

  • 相比表格:Grid 不改变语义,不会引入无意义的 table 结构,且支持响应式重排。
  • 相比 Flexbox:Flexbox 做不到“左侧标签列宽统一”,因为每个表单项都是独立的 Flex 容器,标签宽度各不相同。Grid 的两列模板天然保证了列宽一致。

这是一个非常典型的“二维问题用 Grid”的例子:表单的对齐需要同时考虑行和列,Flexbox 只能逐行处理。

十八、实战六:图片画廊与瀑布流近似方案

真正的瀑布流(Masonry)需要等图片加载完才知道高度,纯 CSS 一直做不到。不过用 Grid 的 dense 模式配合不同的 span,可以做出视觉上非常接近瀑布流的效果。

/* 近似瀑布流:固定行高 + 随机跨度 */
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  grid-auto-rows: 10px; /* 细粒度行高 */
  grid-auto-flow: dense;
  gap: 12px;
}

/* 每张图根据自己的宽高比占不同的行数 */
.gallery img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 8px;
}

/* JS 根据图片比例设置 span */
const rowUnit = 10;
const gap = 12;
img.onload = () => {
  const rows = Math.ceil((img.height / img.width) * 180 / (rowUnit + gap));
  img.parentElement.style.gridRowEnd = `span ${rows}`;
};

/* 更精确的做法:CSS 多列布局 */
.masonry-columns {
  columns: 3 240px;
  column-gap: 16px;
}
.masonry-columns > * {
  break-inside: avoid;
  margin-bottom: 16px;
}
Grid + dense 的紧凑排布

该选哪种方案?

方案 优点 缺点
Grid + dense 纯 CSS,元素顺序可预测,性能好 需要 JS 计算 span,非真正瀑布流
CSS columns 真正的瀑布流效果,实现极简 阅读顺序是纵向的,不利于内容列表
JS 绝对定位 完全可控 性能差、需要监听容器尺寸变化、维护成本高
grid-template-rows: masonry 真正的原生瀑布流 规范仍在推进,浏览器支持有限

十九、Grid 与 Flexbox:到底该用谁

这是一个被问烂了的问题,但答案其实很清晰:看你的布局是几维的。

/* 场景 1:导航栏 —— 用 Flexbox */
/* 一维排列,元素宽度由内容决定 */
.nav {
  display: flex;
  align-items: center;
  gap: 24px;
}

/* 场景 2:卡片网格 —— 用 Grid */
/* 二维排列,需要行与列同时对齐 */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

/* 场景 3:卡片内部 —— 又用回 Flexbox */
/* 纵向排列,按钮贴底 */
.card {
  display: flex;
  flex-direction: column;
}
.card .btn { margin-top: auto; }

/* 结论:Grid 搭骨架,Flexbox 做组件 */
用 Flexbox 导航栏、工具栏、按钮组、标签列表、卡片内部
用 Grid 整页骨架、卡片网格、仪表盘、表单、杂志排版
都可以 单行居中、等分按钮——选可读性更好的那个
经验法则 需要“对齐行列”就用 Grid,需要“分配空间”就用 Flex

一个反直觉的事实

很多人以为 Grid 一定比 Flexbox “重”。实际上,对于一个简单的单行等分布局,两者的渲染性能几乎没有差别。真正影响性能的是你动画了哪些属性(见上一篇《高性能 CSS 动画技巧》),而不是你用了哪种布局方式。

所以选择标准应该回归到可读性与可维护性:哪种写法更能表达你的布局意图,就用哪种。

二十、常见陷阱清单

下面这些坑,几乎每个用 Grid 的人都会踩一遍。提前知道,可以省下大量调试时间。

陷阱 1:用 1fr 却被内容撑爆
grid-template-columns: 1fr 1fr;
列里有长 URL,列宽被撑开。改为 minmax(0, 1fr)。
原因
1fr 的隐含最小值是 auto,不会小于内容的 min-content 宽度。这是规范定义的,不是 bug。
陷阱 2:grid-template-areas 列数不一致
"header header"
"main side extra"
两行列数不同,整条声明会完全失效。
解决
每一行的字符串数量必须完全相同。用点号 . 占位补齐,而不是省略。
陷阱 3:把非直接子元素当作网格项目
<div class="grid"><div><span>...</span></div></div>
span 不是网格项目,grid-column 对它无效。
解决
只有直接子元素才是网格项目。如果确实需要,给中间层加 display: contents。
陷阱 4:忘了 gap 也会参与空间计算
grid-template-columns: repeat(3, 33.33%); gap: 20px;
三列总宽 99.99%,再加上两个 gap,直接横向溢出。
解决
用 1fr 而不是百分比。浏览器会自动把 gap 从可用空间里扣除,再分配 fr。
陷阱 5:dense 打乱阅读顺序
在新闻列表、商品列表中使用 grid-auto-flow: dense,导致视觉顺序与 DOM 顺序不一致,屏幕阅读器与键盘 Tab 顺序混乱。
解决
只在纯装饰性的图片墙使用 dense;内容型列表保留默认的 row。
陷阱 6:在子元素上写 grid-template-columns
.item { grid-template-columns: 1fr 1fr; }
如果 .item 没有 display: grid,这句完全无效。
解决
grid-* 系列属性只对网格容器生效。grid-column / grid-row 写在项目上,其余写在容器上。
陷阱 7:用 Grid 做动态数量的自适应,却不设 auto-fit
grid-template-columns: repeat(3, 1fr); 写死三列,在窄屏上每列只剩 80px。
解决
repeat(auto-fit, minmax(min(280px, 100%), 1fr)),让列数随容器宽度自动变化。
陷阱 8:误以为 Grid 会自动处理超长内容
网格项目默认 min-width: auto,长内容会撑破单元格。
解决
给项目加 min-width: 0,或使用 overflow: hidden / text-overflow: ellipsis。

二十一、无障碍与语义化

Grid 有一个容易被忽略的巨大优势:它允许视觉顺序与 DOM 顺序解耦,同时不破坏语义。这对无障碍来说意义重大。

/* ✅ 推荐:DOM 顺序符合阅读逻辑 */
<main class="layout">
  <header>...</header>
  <main>...</main>    /* 主内容在前 */
  <aside>...</aside>
  <footer>...</footer>
</main>

/* 通过 areas 让 aside 在视觉上出现在右侧 */
.layout {
  display: grid;
  grid-template-columns: 1fr 300px;
  grid-template-areas:
    "header header"
    "main aside"
    "footer footer";
}

/* ❌ 反例:为了视觉效果调换 DOM 顺序 */
/* <aside> 写在 <main> 前面 */
/* 屏幕阅读器会先朗读侧边栏,用户要跳过一堆链接才能读到正文 */
好处一 视觉顺序自由,DOM 顺序保持语义正确
好处二 键盘 Tab 顺序跟随 DOM,不会“跳来跳去”
好处三 SEO 抓取的结构与实际内容一致
例外 dense 会破坏视觉顺序,内容型列表慎用

几个具体的无障碍建议

  1. 优先使用语义标签:header、nav、main、aside、footer。Grid 不影响它们的语义,反而让结构更清晰。
  2. 不要用 order 或密集流打乱焦点顺序:键盘用户的 Tab 顺序必须与视觉顺序基本一致。
  3. 给网格容器加合适的 role:如果网格是数据表格性质,考虑用真正的 <table>;如果是纯布局,不要加多余的 ARIA 角色。
  4. 尊重 prefers-reduced-motion:如果网格布局切换时带有动画过渡,需要在减少动效模式下关闭。

二十二、兼容性与渐进增强

Grid 的兼容性在 2026 年已经非常好了。核心特性几乎全平台可用,只有少数新特性需要留意。

grid-template-columns / rows grid-template-areas auto-fit / auto-fill minmax() gap grid-auto-flow subgrid(较新) masonry(实验性)
/* 渐进增强:先给一个可用的降级方案 */
.card-grid {
  /* 降级:单列堆叠,所有浏览器都能看 */
  display: block;
}
.card-grid > * {
  margin-bottom: 20px;
}

/* 支持 Grid 时启用网格布局 */
@supports (display: grid) {
  .card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
    gap: 24px;
  }
  .card-grid > * {
    margin-bottom: 0;
  }
}

/* 检测 subgrid 支持 */
@supports (grid-template-rows: subgrid) {
  .card {
    grid-template-rows: subgrid;
  }
}

/* JS 侧的特性检测 */
if (CSS.supports('grid-template-rows', 'subgrid')) {
  /* 启用 subgrid 方案 */
} else {
  /* 回退到固定行高方案 */
}
2017 起 Grid 核心特性在主流浏览器全面可用
2022 起 gap 在 Flexbox 中也可用,统一了间距方案
2023 起 subgrid 在三大引擎陆续落地
进行中 masonry 仍处于规范讨论与实验阶段

二十三、性能:Grid 会影响渲染性能吗

这是一个常见疑问。简短的回答是:Grid 本身对渲染性能的影响非常小,真正影响性能的是网格的复杂度和元素数量。

/* 影响 Grid 布局性能的三个因素 */

/* 1. 轨道数量:轨道越多,布局计算越复杂 */
grid-template-columns: repeat(24, 1fr); /* 比 repeat(3,1fr) 慢 */

/* 2. fr 与 auto 的混合:需要多轮计算 */
grid-template-columns: auto 1fr auto 1fr; /* 比纯 1fr 慢 */

/* 3. 自动填充:auto-fit / auto-fill 需要测量容器 */
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));

/* 优化手段:给网格容器加 contain */
.grid-container {
  contain: layout style;
}

/* 长列表:跳过屏幕外的渲染 */
.grid-item {
  content-visibility: auto;
  contain-intrinsic-size: auto 200px;
}
布局阶段 Grid 的计算比 Flexbox 稍复杂,但差异通常在微秒级
绘制阶段 Grid 不产生额外的绘制,与普通块级布局相同
合成阶段 Grid 不创建合成层,也不影响图层数量
真正的瓶颈 通常是网格里元素太多、或动画了会触发布局的属性

什么时候需要担心

  • 超过 500 个网格项目:考虑虚拟滚动,只渲染可视区域内的元素。
  • 网格尺寸随窗口变化频繁重算:给容器加 contain: layout style,避免影响外部。
  • 网格布局本身在做动画:不要动画 grid-template-columns,它会触发布局。需要动画就用 transform。
  • 网格中包含大量图片:给图片设置明确的宽高比,避免布局抖动(CLS)。
/* 用 aspect-ratio 避免图片加载导致的布局抖动 */
.gallery img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  display: block;
}

二十四、最佳实践清单与可复用代码

把全文的核心结论浓缩成一份可以贴在工位上的清单。

  • 二维用 Grid,一维用 Flexbox:需要行列同时对齐就用 Grid,只是单行排列就用 Flex。
  • 响应式卡片网格用 repeat(auto-fit, minmax(min(280px, 100%), 1fr)):一行顶十行媒体查询。
  • 需要严格等分时用 minmax(0, 1fr):避免长内容把网格撑爆。
  • 整页骨架用 grid-template-areas:代码即布局图,评审和维护都轻松。
  • 响应式重排优先改 areas,而不是改 DOM 顺序:保住语义与无障碍。
  • 间距一律用 gap:不要再用 margin 做网格间距。
  • 网格项目默认 min-width: auto:需要防溢出时显式设置 min-width: 0。
  • 跨满整行用 grid-column: 1 / -1:不需要知道有多少列。
  • 重叠元素直接用同一个 grid-area:不需要绝对定位。
  • 卡片内部用 Flexbox:Grid 管外部排列,Flexbox 管内部结构。
  • 长列表加 contain: layout paint:把重绘范围锁在局部。
  • 图片加 aspect-ratio:避免加载时布局抖动。
  • 慎用 grid-auto-flow: dense:它会打乱视觉顺序,影响无障碍。
  • 不要动画 grid-template-*:布局属性动画代价极高。
  • 用 DevTools 的 Grid 检查器:Chrome 可以直接高亮网格线与区域,调试效率翻倍。
/* ========== 可直接复用的 Grid 基线 ========== */

/* 1. 响应式卡片网格 */
.grid-auto-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 24px;
}

/* 2. 固定列数但严格等分 */
.grid-3col {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 20px;
}

/* 3. 侧边栏 + 主内容 */
.grid-with-aside {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 32px;
  align-items: start;
}
@media (max-width: 768px) {
  .grid-with-aside { grid-template-columns: 1fr; }
}

/* 4. 整页骨架 */
.page-shell {
  display: grid;
  grid-template-rows: auto 1fr auto;
  min-height: 100vh;
}

/* 5. 水平垂直居中 */
.grid-center {
  display: grid;
  place-items: center;
}

/* 6. 安全溢出:所有网格容器的通用防御 */
.grid-safe > * {
  min-width: 0;
  min-height: 0;
}

Grid 的学习曲线,其实比它看起来要平缓得多。真正需要跨过的坎只有三个:理解网格线的编号规则、记住 1fr 与 minmax(0, 1fr) 的区别、习惯用 grid-template-areas 描述整体结构。跨过之后你会发现,过去那些需要反复调整像素的布局,现在只需要改一行字符串。

建议你把这篇文章里的几个实验台反复玩几遍:拖动 fr 滑块观察比例变化,切换 auto-fit 与 auto-fill 看最后一行的差异,点击预设按钮感受 areas 的表达力。布局这件事,看十遍不如亲手改一次。当你能在脑子里“看见”网格线,并直接把想法翻译成几行 CSS 时,Grid 就真正属于你了。