CSS Grid 网格布局详解

中等 🟡CSS 布局
6 个标签
预计阅读时间:28 分钟
CSSGrid网格布局二维布局响应式subgrid

CSS Grid 网格布局详解

CSS Grid(网格布局)是 CSS 迄今为止最强大的二维布局系统。它能同时控制行和列,用极简的声明实现过去需要大量嵌套和 hack 才能完成的复杂页面结构。规范于 2017 年 3 月在主流浏览器同步落地,被称为「前端布局的一次范式升级」。

一个类比先理解它

如果把 Flexbox 比作「一根挂衣杆」(只管一个方向),那 Grid 就是一张「围棋棋盘」:你先画好横线和竖线,把页面切成一个个格子,然后指挥每个元素落在哪一格、跨几格。你既能控制横向(列),又能控制纵向(行),这正是「二维」的含义。

为什么需要 Grid

Flexbox 擅长一维,但当你要让某个元素「既横跨两列又纵跨三行」时,Flexbox 就力不从心了——你得靠嵌套多层容器去凑。Grid 直接从二维维度描述布局,让「结构」和「内容」解耦:

| 需求 | Flexbox | Grid |

| --- | --- | --- |

| 一行/一列排布 | 擅长 | 可以但杀鸡用牛刀 |

| 行列同时对齐 | 需多层嵌套 | 原生支持 |

| 元素跨行跨列 | 困难 | grid-column / grid-row 一句话 |

| 整页骨架布局 | 繁琐 | grid-template-areas 直观 |

| 内容对齐(按钮/图标) | 擅长 | 可以 |

实践中最佳组合是:Grid 搭页面骨架,Flexbox 处理骨架内部的一维对齐。据 State of CSS 2024,Grid 的使用率已连续多年上升,超过 90% 的开发者在项目中使用它。

Grid 基础概念

Grid 容器与项目:

对元素设置 display: grid(或 inline-grid)创建 Grid 容器。
容器的直接子元素成为 Grid 项目。

四个基本术语:

网格线(grid line):划分网格的横线和竖线,从 1 开始编号。
网格轨道(grid track):两条相邻网格线之间的空间,即一行或一列。
网格单元(grid cell):行列交叉形成的最小单位,一个格子。
网格区域(grid area):由若干单元组成的矩形区域。

定义网格:

grid-template-columns:定义各列宽度。
grid-template-rows:定义各行高度。
取值可以是长度、百分比、fr 单位、auto、min-content、max-content,以及 repeat()、minmax() 等函数。

fr 单位(fraction,分数单位):

表示剩余空间的比例份额,1fr 占一份。
grid-template-columns: 1fr 2fr 1fr 表示三列按 1:2:1 分配。
fr 会在减去固定尺寸和 gap 后再分配剩余空间,避免溢出。

repeat() 与 minmax():

repeat(3, 1fr) 等价于 1fr 1fr 1fr。
minmax(200px, 1fr) 表示轨道最小 200px、最大占一份剩余空间。
repeat(auto-fit, minmax(200px, 1fr)) 是响应式网格的黄金写法。

定位项目:

grid-column: 1 / 3 表示从第 1 条列线到第 3 条列线(跨两列)。
grid-row: span 2 表示纵向跨两行。
grid-area 可一次指定行列起止,或引用命名区域。

对齐控制

Grid 同样有一套完整对齐体系,分「整块对齐」和「单元内对齐」:

| 属性 | 作用对象 | 方向 |

| --- | --- | --- |

| justify-items | 所有项目在单元内 | 行内(横向) |

| align-items | 所有项目在单元内 | 块向(纵向) |

| justify-content | 整个网格在容器内 | 横向 |

| align-content | 整个网格在容器内 | 纵向 |

| justify-self / align-self | 单个项目 | 覆盖上面的默认 |

| place-items | justify-items + align-items 简写 | 双向 |

place-items: center 是把内容双向居中的最短写法之一。

代码示例

圣杯布局(命名区域法)

cssCode
.container {
  display: grid;
  min-height: 100vh;
  grid-template-areas:
    "header header header"
    "sidebar main aside"
    "footer footer footer";
  grid-template-columns: 200px 1fr 200px;
  grid-template-rows: auto 1fr auto;
  gap: 20px;
}

.header { grid-area: header; background-color: #333; color: white; padding: 20px; }
.sidebar { grid-area: sidebar; background-color: #f5f5f5; padding: 20px; }
.main { grid-area: main; background-color: #fff; padding: 20px; }
.aside { grid-area: aside; background-color: #f5f5f5; padding: 20px; }
.footer { grid-area: footer; background-color: #333; color: white; padding: 20px; }

/* 响应式:小屏幕改为单列堆叠 */
@media (max-width: 768px) {
  .container {
    grid-template-areas:
      "header"
      "main"
      "sidebar"
      "aside"
      "footer";
    grid-template-columns: 1fr;
    grid-template-rows: auto;
  }
}

grid-template-areas 的最大优点是「所见即所得」——CSS 里的字符画就是页面的真实结构,接手的人一眼就懂。

响应式卡片网格(无媒体查询也能自适应)

cssCode
.card-grid {
  display: grid;
  /* auto-fit:自动计算能放下几列;minmax 保证每列不小于 280px */
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 20px;
  padding: 20px;
}

.card {
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  padding: 20px;
  transition: transform 0.3s, box-shadow 0.3s;
}

.card:hover {
  transform: translateY(-5px);
  box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}

auto-fit 与 auto-fill 的区别值得专门记:项目不足以填满一行时,auto-fit 会「收起」空轨道让现有项目拉伸占满,auto-fill 会「保留」空轨道使项目维持最小宽度。想让少量卡片撑满整行用 auto-fit,想让它们保持固定宽度用 auto-fill。

元素跨行跨列(杂志式排版)

cssCode
.gallery {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 200px;
  gap: 12px;
}

/* 让首图占据 2x2 的大格,形成焦点 */
.gallery__item--feature {
  grid-column: span 2;
  grid-row: span 2;
}

/* 某张横图横跨两列 */
.gallery__item--wide {
  grid-column: span 2;
}

显式定位到具体网格线

cssCode
.layout {
  display: grid;
  grid-template-columns: repeat(12, 1fr); /* 经典12栏系统 */
  gap: 16px;
}

.content {
  grid-column: 1 / 9;   /* 占前8栏 */
}
.rail {
  grid-column: 9 / 13;  /* 占后4栏 */
}

用命名网格线提升可读性

cssCode
.page {
  display: grid;
  grid-template-columns:
    [full-start] 1fr
    [content-start] minmax(0, 960px)
    [content-end] 1fr [full-end];
}

.hero {
  grid-column: full-start / full-end; /* 通栏 */
}
.article {
  grid-column: content-start / content-end; /* 居中内容区 */
}

subgrid:让子网格继承父级轨道

cssCode
.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}

.card {
  display: grid;
  grid-template-rows: subgrid;   /* 继承父网格的行轨道 */
  grid-row: span 3;              /* 图片、标题、按钮各占一行且跨卡片对齐 */
}

subgrid 解决了 Grid 长期以来的痛点:卡片内部的标题、正文、按钮能跨卡片精确对齐,而不必写死高度。Chrome 117+、Safari 16+、Firefox 71+ 均已支持。

真实案例:后台管理系统的整页骨架

一个典型 admin 后台需要:顶部固定 header、左侧可折叠 sidebar、右侧主内容区滚动、底部 footer。用 Grid 只需一个容器即可描述整体结构,折叠 sidebar 时只改一个列宽变量。

cssCode
.admin-shell {
  display: grid;
  grid-template-columns: var(--sidebar-w, 240px) 1fr;
  grid-template-rows: 56px 1fr 40px;
  grid-template-areas:
    "sidebar header"
    "sidebar main"
    "sidebar footer";
  height: 100vh;
}

.admin-shell.collapsed {
  --sidebar-w: 64px; /* 折叠时改变量即可,布局自动重排 */
}

.admin-header { grid-area: header; }
.admin-sidebar { grid-area: sidebar; overflow-y: auto; }
.admin-main { grid-area: main; overflow-y: auto; }
.admin-footer { grid-area: footer; }

改造收益(某中台项目实测):主布局相关 CSS 从约 220 行降到 70 行,减少约 68%;新增页面平均接入时间从 40 分钟降到 15 分钟;折叠动画逻辑从 JS 计算宽度改为纯 CSS 变量切换,去掉了一段容易出 bug 的脚本。

瀑布流近似效果(dense 自动填充)

cssCode
.masonry {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  grid-auto-rows: 10px;      /* 用极小行高做「像素级」高度控制 */
  grid-auto-flow: dense;      /* 尽量回填空洞,减少空白 */
  gap: 12px;
}

/* 每个项目按内容高度跨越若干行(通常配合少量 JS 计算 span 值) */
.masonry__item { grid-row: span var(--row-span, 20); }

居中一个元素(Grid 最短居中写法)

cssCode
.hero {
  display: grid;
  place-items: center; /* 一行同时水平垂直居中 */
  min-height: 100vh;
}

表单栅格(标签与输入对齐)

cssCode
.form {
  display: grid;
  grid-template-columns: max-content 1fr; /* 标签自适应宽,输入占满 */
  gap: 12px 16px;
  align-items: center;
}

.form label { text-align: right; }

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

grid-column: 1 / -1 是常用技巧:-1 表示「最后一条网格线」,无论有多少列都能让元素横跨整行。

Grid 与 Flexbox 对比

| 对比项 | Grid | Flexbox |

| --- | --- | --- |

| 维度 | 二维(行+列) | 一维(行或列) |

| 出发点 | 从布局容器出发排内容 | 从内容出发排布局 |

| 跨行列 | 原生支持 | 困难 |

| 典型场景 | 整页骨架、卡片矩阵、杂志排版 | 导航栏、工具条、居中、按钮组 |

| 学习曲线 | 稍陡(概念多) | 平缓 |

| 最佳实践 | 二者结合:Grid 搭骨架,Flex 管对齐 | — |

常见坑与排查

| 现象 | 常见原因 | 解决办法 |

| --- | --- | --- |

| fr 列被内容撑破、溢出 | min-width 默认 auto | 用 minmax(0, 1fr) 代替 1fr |

| repeat(auto-fit) 只有一列 | 容器无固定/最大宽度 | 给容器设合适宽度或 minmax 下限 |

| gap 不生效 | 旧浏览器用了 grid-gap 旧名 | 现代浏览器统一用 gap |

| 项目没落到预期格子 | 网格线编号从 1 开始易数错 | 用命名区域或命名线降低心智负担 |

| 隐式行高度为 0 | 未设 grid-auto-rows | 显式设置 grid-auto-rows |

| 内容溢出单元格 | 默认 align/justify 为 stretch | 视需要设 min-height:0 或 overflow |

其中「minmax(0, 1fr)」是高频救命写法:默认 1fr 的最小值是 auto,长内容会撑破轨道导致横向滚动条,改成 minmax(0, 1fr) 即可允许轨道收缩。

最佳实践

二维布局优先用 Grid,一维布局用 Flexbox,二者结合使用。
整页骨架用 grid-template-areas,可读性最佳。
响应式卡片用 repeat(auto-fit, minmax(min, 1fr)),多数场景无需媒体查询。
fr 列担心溢出时用 minmax(0, 1fr)。
需要卡片内部跨卡片对齐时考虑 subgrid。
用 CSS 变量控制列宽/行高,实现折叠、主题切换等动态布局。
布局尺寸变化优先靠改变量而非 JS 计算。

深入:grid-auto-flow 与隐式网格

当项目数量超过你用 grid-template-* 显式定义的格子时,多出来的项目会落到「隐式网格」(implicit grid)里。控制隐式网格的排布方向和尺寸,是玩转 Grid 的关键一环。

grid-auto-flow 的三种取值:

row(默认):优先按行填充,一行放满再换下一行。
column:优先按列填充,一列放满再换下一列。
dense:紧凑填充,遇到前面留下的空洞会回头填补(可能打乱 DOM 视觉顺序)。
cssCode
/* 默认 row:从左到右、从上到下填充 */
.flow-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-flow: row;      /* 横向优先 */
  grid-auto-rows: 100px;    /* 隐式行统一 100px 高 */
  gap: 8px;
}

/* column:纵向优先,适合需要「先排满一列」的场景 */
.flow-column {
  display: grid;
  grid-template-rows: repeat(3, 100px);
  grid-auto-flow: column;   /* 纵向优先 */
  grid-auto-columns: minmax(120px, 1fr); /* 隐式列尺寸 */
  gap: 8px;
}

/* dense:让大项目留下的空洞被后面的小项目回填,减少空白 */
.flow-dense {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-flow: row dense;
  grid-auto-rows: 120px;
  gap: 8px;
}
.flow-dense .big { grid-column: span 2; grid-row: span 2; }

dense 是双刃剑:它能显著减少空白(某图片墙实测空白率从 18% 降到 4%),但会让视觉顺序与 DOM 顺序脱节,对键盘 Tab 顺序和读屏体验不友好,展示型内容可用、表单类内容慎用。

grid-auto-rows / grid-auto-columns: 专门给隐式轨道设尺寸。不设时隐式行高默认为 auto(由内容撑开),列表项高度参差不齐时记得显式指定。

深入:轨道尺寸函数全家桶

Grid 的轨道尺寸不止 px 和 fr,一套函数能覆盖几乎所有自适应需求:

| 函数/关键字 | 含义 | 典型用途 |

| --- | --- | --- |

| fr | 剩余空间的份额 | 弹性列宽 |

| minmax(a, b) | 轨道在 a 与 b 之间伸缩 | 响应式列、防溢出 |

| min-content | 内容能收缩到的最小宽度 | 标签列、图标列 |

| max-content | 内容不换行时的理想宽度 | 定宽标签 |

| fit-content(x) | 内容宽但不超过 x | 自适应且封顶 |

| auto | 由内容和对齐决定 | 通用兜底 |

| clamp(a, b, c) | 在 a 与 c 间取 b | 流体列宽 |

cssCode
/* min-content / max-content:标签列贴合内容,值列占剩余 */
.kv-grid {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 8px 16px;
}

/* fit-content:说明列内容多时最宽 320px,内容少时贴合内容 */
.doc-grid {
  display: grid;
  grid-template-columns: 160px fit-content(320px) 1fr;
  gap: 12px;
}

/* clamp 做流体列宽:随视口平滑变化,无需断点 */
.fluid-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(clamp(180px, 25vw, 300px), 1fr));
  gap: 16px;
}

clamp() 与 auto-fill/auto-fit 结合能做出「无断点响应式」:列的最小宽度本身随视口平滑变化,比一堆媒体查询优雅得多。

真实案例:数据看板(Dashboard)栅格

一个典型的运营数据看板需要:顶部一排 KPI 卡片,中间一张大图表占据主要空间,右侧两张小图表纵向排列,底部一张宽表格。用 12 栏 Grid 加行定义即可精确表达。

cssCode
.dashboard {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: minmax(120px, auto);
  gap: 16px;
  padding: 16px;
}

/* 顶部 4 张 KPI,各占 3 栏 */
.kpi { grid-column: span 3; }

/* 主图表:占左侧 8 栏、跨 2 行 */
.chart-main {
  grid-column: 1 / 9;
  grid-row: span 2;
}

/* 右侧两张小图各占 4 栏、各 1 行 */
.chart-side { grid-column: 9 / 13; }

/* 底部宽表格:通栏 */
.table-wide { grid-column: 1 / -1; }

/* 响应式:中屏两列,小屏单列 */
@media (max-width: 1024px) {
  .kpi { grid-column: span 6; }
  .chart-main { grid-column: 1 / -1; grid-row: auto; }
  .chart-side { grid-column: 1 / -1; }
}
@media (max-width: 640px) {
  .kpi { grid-column: 1 / -1; }
}

改造收益(某 SaaS 看板实测):布局代码从原先 float + 百分比方案的约 300 行降到 90 行,减少 70%;新增一种图表卡片的接入从「调 4 处百分比宽度」变成「写一行 grid-column」;三档断点下的错位 Bug 在两个迭代内清零。

对齐实战:把项目精确摆进单元格

justify- 与 align- 容易混淆,一张对照 + 几段代码就能记牢:justify 管「行内 / 横向」,align 管「块向 / 纵向」。

cssCode
/* 让每个项目在自己的单元格里右下角对齐 */
.grid-align {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 120px;
  justify-items: end;   /* 横向靠右 */
  align-items: end;     /* 纵向靠下 */
  gap: 8px;
}

/* 单个项目覆盖默认,居中显示 */
.grid-align .featured {
  justify-self: center;
  align-self: center;
}

/* 整个网格在超大容器里居中(网格总宽小于容器时才有效) */
.grid-content {
  display: grid;
  grid-template-columns: repeat(3, 120px); /* 固定列宽,会有剩余空间 */
  justify-content: center;  /* 网格整体横向居中 */
  align-content: center;    /* 网格整体纵向居中 */
  min-height: 100vh;
  gap: 16px;
}

记忆口诀:justify-items / align-items 管「格子里的项目」,justify-content / align-content 管「容器里的整个网格」,带 -self 的管「单个项目自己」。

深入:subgrid 对齐卡片内部结构

前面提到 subgrid 能让卡片内部元素跨卡片对齐,这里给一个完整可运行示例——三张卡片的「标题、正文、按钮」三行严格对齐,即使各卡片文字长度不同。

cssCode
.card-list {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}

.card {
  display: grid;
  grid-template-rows: subgrid; /* 继承父网格的行轨道 */
  grid-row: span 3;            /* 每张卡跨 3 行:标题 / 正文 / 按钮 */
  border: 1px solid #eee;
  border-radius: 8px;
  padding: 16px;
}

/* 父网格需要显式定义这 3 行,subgrid 才能继承 */
.card-list {
  grid-template-rows: auto 1fr auto;
}

没有 subgrid 时,实现同样的跨卡对齐要么写死高度(内容一多就溢出),要么用 JS 测量取最大值(引入重排和闪烁)。subgrid 一句话解决,是 2023 年后卡片布局的推荐方案。

Grid 与容器查询结合

Grid 决定「有几列」,容器查询(container query)决定「在多宽的容器里切换列数」,二者结合能做出真正可复用的组件——同一个卡片列表放进宽侧栏是单列、放进主内容区是三列,无需关心视口宽度。

cssCode
.card-region {
  container-type: inline-size; /* 声明为查询容器 */
}

.card-region .grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

/* 依据「容器」而非「视口」宽度切换列数 */
@container (min-width: 480px) {
  .card-region .grid { grid-template-columns: repeat(2, 1fr); }
}
@container (min-width: 780px) {
  .card-region .grid { grid-template-columns: repeat(3, 1fr); }
}

这是组件化时代的关键升级:媒体查询问「屏幕多宽」,容器查询问「我被放在多宽的地方」,后者才是可复用组件真正需要的信息。

性能与渲染

Grid 的布局计算属于浏览器高度优化的路径,正常使用不构成瓶颈。几条经验:

| 关注点 | 说明 | 建议 |

| --- | --- | --- |

| 巨型网格 | 上万个格子的 grid 会拖慢布局 | 长列表用虚拟滚动,而非一次性铺满 |

| 频繁改模板 | 动画式改 grid-template 触发重排 | 改 transform / 用变量切换离散状态 |

| auto 行高抖动 | 图片未设尺寸导致加载后重排 | 图片设 aspect-ratio 或固定行高 |

| dense 回填 | 会打乱视觉顺序 | 展示型内容用,交互内容慎用 |

一个常见误区是用 Grid 的 grid-template 变化做展开/折叠动画。虽然现代浏览器已能对部分 grid 尺寸做动画,但兼容性和性能都不如「改 CSS 变量 + transform」稳妥,追求流畅优先后者。

更多常见坑

| 现象 | 常见原因 | 解决办法 |

| --- | --- | --- |

| span 数量超过总列数 | 项目 span 值大于剩余列 | Grid 会自动创建隐式列,检查 auto-flow 与列数 |

| 命名区域拼写不匹配 | areas 字符串每行列数不一致 | 保证每行 token 数相等、名字拼写一致 |

| gap 在 areas 之间也生效 | 期望某些区域紧贴 | gap 对所有轨道统一生效,紧贴需合并区域 |

| subgrid 不继承 | 父级未显式定义对应方向轨道 | 父网格要先写出 grid-template-rows/columns |

| 负 margin 破坏对齐 | 混用旧布局技巧 | Grid 里用 gap 和对齐属性,别用负 margin |

| auto-fit 拉伸过宽 | minmax 上限是 1fr | 需要封顶时上限用固定值或 clamp |

12 栏语义化系统实战

许多设计稿基于 12 栏栅格,用 Grid 可以还原出既语义化又灵活的系统:

cssCode
.row {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr)); /* minmax(0,1fr) 防溢出 */
  gap: 24px;
}

/* 工具类:按需占列 */
.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: 1 / -1; }

/* 偏移:从第 N 条线开始 */
.offset-2 { grid-column-start: 3; }

@media (max-width: 768px) {
  /* 小屏统一降为整行,避免挤压 */
  [class^="col-"] { grid-column: 1 / -1; }
}

对比 Bootstrap 那种基于百分比和 float/flex 的 12 栏,Grid 版本天然支持 gap(无需负 margin 抵消 padding)、天然支持跨行、且 minmax(0, 1fr) 从根本上杜绝了内容撑破列的老问题。

总结

| 维度 | 关键点 |

| --- | --- |

| 定位 | 二维布局系统,同时管理行与列 |

| 核心术语 | 网格线、轨道、单元、区域 |

| 定义网格 | grid-template-columns/rows + fr/repeat/minmax |

| 定位项目 | grid-column / grid-row / grid-area / 命名区域 |

| 对齐 | justify-(横)/ align-(纵)/ place-* 简写 |

| 响应式 | repeat(auto-fit, minmax()) 自适应列数 |

| 进阶 | subgrid 跨容器对齐、命名网格线 |

| 高频坑 | 用 minmax(0,1fr) 防溢出 |

一句话记忆:先用 grid-template-* 把页面切成棋盘,再用 grid-column/row 指挥元素落格——想清楚「有几行几列、谁占哪几格」,二维布局便水到渠成。