Skip to content

Grid 布局

Grid 布局模型概述

CSS Grid Layout 是一个二维布局系统,可以同时控制行和列。与 Flexbox 的一维布局(一次只处理一个方向)不同,Grid 能让你先定义一个网格结构,再将元素放置到网格中的任意位置

Grid 的设计哲学是布局驱动——先定义网格框架,再让内容填入其中。这与 Flex 的内容驱动(内容决定布局)形成互补。两者不是替代关系,而是针对不同场景的最佳选择。

核心术语

        列轨道 (Column Track)
         ↓    ↓    ↓
       col1  col2  col3
        ↓    ↓    ↓
  ┌─────────┬─────────┬─────────┐ ← 网格线 (Grid Line) 1
  │         │         │         │
  │ Cell    │ Cell    │ Cell    │ ← 行轨道 (Row Track) 1
  │ (1,1)   │ (1,2)   │ (1,3)   │
  ├─────────┼─────────┼─────────┤ ← 网格线 2
  │         │         │         │
  │ Cell    │ Cell    │ Cell    │ ← 行轨道 2
  │ (2,1)   │ (2,2)   │ (2,3)   │
  ├─────────┼─────────┼─────────┤ ← 网格线 3
  │         │         │         │
  │ Cell    │ Cell    │ Cell    │ ← 行轨道 3
  │ (3,1)   │ (3,2)   │ (3,3)   │
  └─────────┴─────────┴─────────┘ ← 网格线 4
  ↑                              ↑
网格线 1                       网格线 4(列方向)
术语说明
Grid Container设置了 display: grid 的元素
Grid ItemGrid Container 的直接子元素
Grid Line网格线,构成网格结构的分界线,编号从 1 开始
Grid Track两条相邻网格线之间的空间(即一行或一列)
Grid Cell四条网格线围成的最小单元格
Grid Area一个或多个 Grid Cell 组成的矩形区域

网格线编号规则:从 1 开始计数。一个 3 列的网格有 4 条列网格线(1-4),一个 3 行的网格有 4 条行网格线(1-4)。也可以用负数从末尾计数,-1 表示最后一条线。


容器属性

display: grid / inline-grid

css
.container {
  display: grid;        /* 块级 Grid 容器 */
  /* display: inline-grid; */ /* 行内 Grid 容器 */
}

grid-template-columns / grid-template-rows

定义网格的列轨道和行轨道大小。这是 Grid 布局最核心的属性。

css
.container {
  grid-template-columns: 200px 1fr 200px;  /* 三列:固定-自适应-固定 */
  grid-template-rows: 80px 1fr 60px;       /* 三行:固定-自适应-固定 */
}

尺寸单位

css
.container {
  grid-template-columns:
    200px              /* 固定像素 */
    1fr                /* 弹性比例单位 */
    30%                /* 百分比 */
    auto               /* 由内容决定 */
    min-content        /* 内容最小宽度 */
    max-content        /* 内容最大宽度(不换行) */
    minmax(200px, 1fr) /* 最小 200px,最大 1fr */
    fit-content(300px) /* 由内容决定,但不超过 300px */
  ;
}

fr 单位详解

fr(fraction)是 Grid 专属的弹性单位,表示剩余空间的等分份额

css
.container {
  width: 900px;
  grid-template-columns: 1fr 2fr 1fr;
}
总可用空间 = 900px
fr 总和 = 1 + 2 + 1 = 4

第 1 列 = 900 × 1/4 = 225px
第 2 列 = 900 × 2/4 = 450px
第 3 列 = 900 × 1/4 = 225px

fr 与固定单位混用时,先减去固定部分,剩余空间再按 fr 分配

css
.container {
  width: 900px;
  grid-template-columns: 200px 1fr 2fr;
}
剩余空间 = 900 - 200 = 700px
第 1 列 = 200px(固定)
第 2 列 = 700 × 1/3 ≈ 233px
第 3 列 = 700 × 2/3 ≈ 467px

repeat() 函数

避免重复书写相同的轨道定义:

css
.container {
  grid-template-columns: repeat(3, 1fr);              /* 三列等宽 */
  grid-template-columns: repeat(3, 100px 200px);      /* 六列:100 200 100 200 100 200 */
  grid-template-columns: repeat(auto-fill, 200px);    /* 自动填充尽可能多的 200px 列 */
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); /* 响应式网格 */
}

auto-fill vs auto-fit

两者在容器宽度大于所有项目总宽度时表现不同:

容器宽度 800px,3 个项目,每个 200px

auto-fill: 创建 4 列(尽可能多的列),第 4 列为空
  [Item1 200px] [Item2 200px] [Item3 200px] [空 200px]

auto-fit:  创建 3 列,空列被折叠为 0,多余空间分配给现有列
  [Item1 ~267px] [Item2 ~267px] [Item3 ~267px]

auto-fill 保留空轨道,auto-fit 折叠空轨道。大多数响应式布局场景使用 auto-fit + minmax() 组合。

minmax() 函数

定义轨道尺寸的范围:

css
.container {
  grid-template-columns: minmax(200px, 1fr) minmax(300px, 2fr) minmax(100px, 1fr);
}

minmax(min, max) 的含义:轨道尺寸不小于 min,不大于 max。当可用空间不足时取 min,空间充裕时按 max 规则分配。

grid-template-areas

通过命名区域直接定义网格布局,这是 Grid 最具表现力的特性——所见即所得

css
.container {
  display: grid;
  grid-template-columns: 200px 1fr 200px;
  grid-template-rows: 80px 1fr 60px;
  grid-template-areas:
    "header  header  header"
    "sidebar content aside"
    "footer  footer  footer";
}

.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.aside   { grid-area: aside; }
.footer  { grid-area: footer; }
┌──────────────────────────────────┐
│            header                │  80px
├────────┬───────────────┬─────────┤
│        │               │         │
│sidebar │   content     │  aside  │  1fr
│        │               │         │
├────────┴───────────────┴─────────┤
│            footer                │  60px
└──────────────────────────────────┘
  200px        1fr          200px

命名区域的规则

  • 每个字符串代表一行,空格分隔各列
  • 同名区域必须组成一个矩形(不能是 L 形或 T 形)
  • 使用 . 表示空白单元格:"header header ." 表示第三列为空

命名区域自动生成网格线:定义 grid-area: header 后,会自动生成 header-startheader-end 网格线,可以在其他元素中引用。

gap / row-gap / column-gap

控制网格单元格之间的间距(不影响容器边缘)。

css
.container {
  gap: 20px;           /* 行间距和列间距都是 20px */
  gap: 20px 10px;      /* 行间距 20px,列间距 10px */
  row-gap: 20px;       /* 单独设置行间距 */
  column-gap: 10px;    /* 单独设置列间距 */
}
┌──────┐  10px  ┌──────┐  10px  ┌──────┐
│      │ ←───→ │      │ ←───→ │      │
│      │        │      │        │      │
└──────┘        └──────┘        └──────┘
   ↕ 20px
┌──────┐        ┌──────┐        ┌──────┐
│      │        │      │        │      │
└──────┘        └──────┘        └──────┘

justify-items / align-items

控制所有 Grid 项目在其各自单元格内的对齐方式。

css
.container {
  justify-items: stretch;  /* 默认值:水平方向拉伸填满单元格 */
  align-items: stretch;    /* 默认值:垂直方向拉伸填满单元格 */
}
css
.container {
  justify-items: start | end | center | stretch;   /* 水平方向 */
  align-items: start | end | center | stretch;     /* 垂直方向 */
  place-items: center;                              /* 简写:同时设置两个方向 */
}
justify-items: start      justify-items: center     justify-items: end
┌─────────────────┐      ┌─────────────────┐       ┌─────────────────┐
│┌───┐            │      │    ┌───┐        │       │            ┌───┐│
││   │            │      │    │   │        │       │            │   ││
│└───┘            │      │    └───┘        │       │            └───┘│
└─────────────────┘      └─────────────────┘       └─────────────────┘

justify-items vs justify-contentjustify-items 控制项目在单元格内的对齐;justify-content 控制整个网格在容器内的对齐。当网格总尺寸小于容器时,justify-content 才有效果。

justify-content / align-content

当网格总尺寸小于容器尺寸时,控制整个网格在容器内的对齐方式。

css
.container {
  justify-content: start | end | center | stretch | space-between | space-around | space-evenly;
  align-content: start | end | center | stretch | space-between | space-around | space-evenly;
  place-content: center;  /* 简写 */
}
justify-content: center(网格整体在容器中水平居中)

┌───────────────────────────────┐
│                               │
│     ┌─────┬─────┬─────┐      │
│     │     │     │     │      │
│     ├─────┼─────┼─────┤      │
│     │     │     │     │      │
│     └─────┴─────┴─────┘      │
│                               │
└───────────────────────────────┘

grid-auto-rows / grid-auto-columns

定义隐式创建的轨道大小。当 Grid 项目的数量超出了 grid-template 定义的范围时,浏览器会自动创建新的轨道,grid-auto-* 控制这些隐式轨道的大小。

css
.container {
  grid-template-rows: 100px 100px;    /* 显式定义 2 行 */
  grid-auto-rows: 80px;               /* 超出部分每行 80px */
  grid-auto-rows: minmax(80px, auto); /* 超出部分最小 80px,自适应 */
}

这在数据列表、卡片网格等项目数量不确定的场景中非常有用——你无需预先知道需要多少行。

grid-auto-flow

控制自动放置算法的方向。

css
.container {
  grid-auto-flow: row;         /* 默认值:先填满一行,再换下一行 */
  grid-auto-flow: column;      /* 先填满一列,再换下一列 */
  grid-auto-flow: row dense;   /* 行优先 + 密集填充(回填空洞) */
  grid-auto-flow: column dense; /* 列优先 + 密集填充 */
}

dense 的作用:默认的自动放置算法是"稀疏"的——如果一个大项目留下了空洞,后续小项目不会回填。dense 关键字启用密集填充算法,允许后续小项目回填前面的空洞。

不用 dense(默认,稀疏):     使用 dense(密集填充):
┌───┬───┬───┐                ┌───┬───┬───┐
│ 1 │ 1 │   │                │ 1 │ 1 │ 3 │  ← 3 回填了空洞
├───┴───┤   │                ├───┴───┤   │
│   2   │   │  ← 空洞        │   2   │   │
├───────┼───┤                ├───────┼───┤
│   3   │ 4 │                │   4   │ 5 │
└───────┴───┘                └───────┴───┘

注意dense 会改变项目的视觉顺序,可能影响无障碍访问。


项目属性

grid-column / grid-row

通过网格线编号指定项目的放置位置。

css
.item {
  grid-column-start: 1;
  grid-column-end: 3;
  grid-row-start: 1;
  grid-row-end: 2;

  /* 简写 */
  grid-column: 1 / 3;   /* 从列线 1 到列线 3(跨 2 列) */
  grid-row: 1 / 2;      /* 从行线 1 到行线 2(跨 1 行) */
}
  列线1    列线2    列线3    列线4
    │       │       │       │
    ┌───────────────┬───────┐ ← 行线 1
    │    Item A     │       │
    │  (1/3, 1/2)   │       │ ← 行线 2
    ├───────┬───────┼───────┤
    │       │       │       │ ← 行线 3
    └───────┴───────┴───────┘

span 关键字

不想数网格线编号?可以用 span 指定跨越的轨道数量:

css
.item {
  grid-column: 1 / span 2;  /* 从列线 1 开始,跨 2 列 */
  grid-row: span 3;         /* 从自动位置开始,跨 3 行 */
}

负数编号

从末尾计数:

css
.item {
  grid-column: 1 / -1;  /* 从第 1 列到最后一列(横跨整行) */
}

grid-area

grid-area 可以用两种方式:

方式一:配合 grid-template-areas 使用命名区域

css
.item { grid-area: header; }

方式二:作为 grid-row-start / grid-column-start / grid-row-end / grid-column-end 的简写

css
.item {
  grid-area: 1 / 1 / 3 / 4;
  /* 等价于:
     grid-row-start: 1;
     grid-column-start: 1;
     grid-row-end: 3;
     grid-column-end: 4;
  */
}

justify-self / align-self

允许单个项目覆盖容器的 justify-items / align-items 设置。

css
.item {
  justify-self: start | end | center | stretch;  /* 水平方向 */
  align-self: start | end | center | stretch;    /* 垂直方向 */
  place-self: center;                             /* 简写 */
}

命名网格线

除了用编号引用网格线,还可以给网格线命名,提高可读性:

css
.container {
  grid-template-columns: [sidebar-start] 200px [sidebar-end content-start] 1fr [content-end];
  grid-template-rows: [header-start] 80px [header-end main-start] 1fr [main-end footer-start] 60px [footer-end];
}

.sidebar {
  grid-column: sidebar-start / sidebar-end;
  grid-row: main-start / main-end;
}

.content {
  grid-column: content-start / content-end;
}

一条网格线可以有多个名称(如 [sidebar-end content-start]),方便不同项目用不同的语义引用同一条线。


隐式网格 vs 显式网格

这是理解 Grid 行为的关键概念。

显式网格:由 grid-template-columnsgrid-template-rowsgrid-template-areas 明确定义的网格。

隐式网格:当 Grid 项目被放置到显式网格之外时(超出定义的行/列范围),浏览器自动创建的额外轨道。

css
.container {
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: 100px 100px;      /* 显式 2 行 */
  grid-auto-rows: 60px;                 /* 隐式行高 60px */
}
┌─────────┬─────────┬─────────┐
│         │         │         │  100px ← 显式
├─────────┼─────────┼─────────┤
│         │         │         │  100px ← 显式
├─────────┼─────────┼─────────┤
│         │         │         │  60px  ← 隐式(grid-auto-rows)
├─────────┼─────────┼─────────┤
│         │         │         │  60px  ← 隐式
└─────────┴─────────┴─────────┘

如果不设置 grid-auto-rows,隐式轨道的默认大小是 auto(由内容决定)。


常见布局实战

css
.page {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-rows: 64px 1fr 48px;
  grid-template-areas:
    "header  header"
    "sidebar content"
    "footer  footer";
  min-height: 100vh;
}

.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.footer  { grid-area: footer; }
┌──────────────────────────────┐
│          header               │  64px
├─────────┬────────────────────┤
│         │                    │
│ sidebar │     content        │  1fr(自适应)
│  240px  │                    │
├─────────┴────────────────────┤
│          footer               │  48px
└──────────────────────────────┘

响应式改造——移动端变为单列:

css
@media (max-width: 768px) {
  .page {
    grid-template-columns: 1fr;
    grid-template-rows: 64px auto 1fr 48px;
    grid-template-areas:
      "header"
      "sidebar"
      "content"
      "footer";
  }
}

响应式卡片网格

无需媒体查询的自适应网格:

css
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 24px;
}
宽屏幕:
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ Card │ │ Card │ │ Card │ │ Card │
└──────┘ └──────┘ └──────┘ └──────┘

中屏幕:
┌──────────┐ ┌──────────┐
│   Card   │ │   Card   │
└──────────┘ └──────────┘
┌──────────┐ ┌──────────┐
│   Card   │ │   Card   │
└──────────┘ └──────────┘

小屏幕:
┌──────────────────┐
│      Card        │
└──────────────────┘
┌──────────────────┐
│      Card        │
└──────────────────┘

repeat(auto-fit, minmax(280px, 1fr)) 的含义:每列最小 280px、最大 1fr,自动计算能放下多少列。这是实现响应式网格最优雅的方式,一行 CSS 替代多个媒体查询。

仪表盘布局

不规则网格中的精确放置:

css
.dashboard {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: 120px 200px 200px;
  gap: 16px;
}

.stat-card { }
.chart-large {
  grid-column: span 2;
  grid-row: span 2;
}
.chart-small {
  grid-column: span 2;
}
┌──────┬──────┬──────┬──────┐
│ Stat │ Stat │ Stat │ Stat │  120px
├──────┴──────┼──────┴──────┤
│             │             │
│ Chart Large │ Chart Large │  200px
│             │             │
├──────┬──────┼─────────────┤
│      │      │             │
│      │      │ Chart Small │  200px
└──────┴──────┴─────────────┘

等高等宽的九宫格

css
.grid-3x3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  aspect-ratio: 1;
  gap: 4px;
}

aspect-ratio: 1 让容器保持正方形,配合 1fr 实现完美的九宫格。

瀑布流布局(CSS Grid 近似方案)

纯 CSS Grid 实现瀑布流需要借助 grid-row: span N 模拟不同高度:

css
.masonry {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  grid-auto-rows: 20px;
  gap: 16px;
}

.item-small  { grid-row: span 8; }
.item-medium { grid-row: span 12; }
.item-large  { grid-row: span 16; }

局限性:CSS Grid 瀑布流需要预先知道或计算项目高度,无法像 Masonry.js 那样根据内容自动排列。W3C 正在制定原生的 masonry 值(grid-template-rows: masonry),但截至 2025 年仍在实验阶段。


Subgrid

Subgrid 是 Grid Level 2 规范中引入的特性,解决了嵌套网格对齐的问题。

问题:嵌套网格无法对齐

css
.outer {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;
}

.inner {
  display: grid;
  grid-template-columns: 1fr 1fr; /* 独立的网格,与外层无关 */
}

内层网格的列与外层网格的列完全独立,无法对齐。

Subgrid 的解决方案

css
.outer {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}

.inner {
  grid-column: span 4;
  display: grid;
  grid-template-columns: subgrid; /* 继承父网格的列轨道定义 */
}

subgrid 让子网格继承父网格的轨道定义,使得子网格的列(或行)与父网格完美对齐。

外层网格:
┌─────┬─────┬─────┬─────┐
│  1  │  2  │  3  │  4  │
├─────┴─────┴─────┴─────┤
│ 内层 grid (subgrid)    │
│ ┌─────┬─────┬─────┬─────┐ ← 列线与外层完全对齐
│ │  A  │  B  │  C  │  D  │
│ └─────┴─────┴─────┴─────┘
└───────────────────────┘

也可以只在一个方向使用 subgrid:

css
.inner {
  display: grid;
  grid-template-columns: subgrid;  /* 列继承父网格 */
  grid-template-rows: 50px 50px;   /* 行独立定义 */
}

典型场景:卡片列表中让所有卡片的标题行、内容行、底部行对齐——即使各卡片内容长短不一。


Grid 布局中的对齐系统总结

Grid 的对齐属性可能是最容易混淆的部分。这里做一个系统梳理:

                  │    容器属性           │   项目属性
──────────────────┼──────────────────────┼──────────────
水平方向(行轴)    │ justify-items        │ justify-self
                  │ justify-content      │
──────────────────┼──────────────────────┼──────────────
垂直方向(块轴)    │ align-items          │ align-self
                  │ align-content        │
──────────────────┼──────────────────────┼──────────────
两个方向(简写)    │ place-items          │ place-self
                  │ place-content        │
属性控制对象作用
justify-items所有项目项目在单元格内的水平对齐
align-items所有项目项目在单元格内的垂直对齐
justify-content整个网格网格在容器内的水平对齐
align-content整个网格网格在容器内的垂直对齐
justify-self单个项目覆盖 justify-items,项目在单元格内的水平对齐
align-self单个项目覆盖 align-items,项目在单元格内的垂直对齐

记忆技巧

  • justify-* → 行轴方向(水平)
  • align-* → 块轴方向(垂直)
  • *-items / *-self → 项目在单元格内的对齐
  • *-content → 网格在容器内的对齐

经典面试题解析

题目一:Grid 和 Flex 应该怎么选?

一维排列用 Flex,二维布局用 Grid,这是最基本的判断标准。

更精细的选择:

场景推荐原因
导航栏Flex一维水平排列,项目数量可能变化
卡片列表两者皆可Flex + wrap 或 Grid + auto-fit
整体页面结构Grid二维布局,header/sidebar/content/footer
表单布局Gridlabel 和 input 的对齐
居中一个元素两者皆可Flex: justify+align / Grid: place-items
仪表盘Grid不规则的二维放置
工具栏按钮组Flex一维排列 + 动态间距

它们可以嵌套使用:外层 Grid 定义页面骨架,内层 Flex 处理各区域内部的排列。

题目二:repeat(auto-fit, minmax(200px, 1fr)) 是如何实现响应式的?

逐步拆解:

  1. minmax(200px, 1fr):每列最小 200px,最大可占 1 个弹性份额
  2. auto-fit:自动计算能放下多少列
  3. 浏览器计算过程:
    • 容器宽度 = 900px,gap = 20px
    • 尝试放 5 列:5 × 200 + 4 × 20 = 1080 > 900 → 不行
    • 尝试放 4 列:4 × 200 + 3 × 20 = 860 ≤ 900 → 可以
    • 剩余空间 = 900 - 860 = 40px → 按 1fr 分配
    • 每列实际宽度 = 200 + 40/4 = 210px
  4. 当容器变窄到 < 420px 时,只能放 1 列,卡片占满宽度

整个过程不需要任何媒体查询。

题目三:什么是网格线编号?为什么从 1 开始?

网格线是轨道之间的分界线,编号从 1 开始(不是从 0)。一个 3 列 × 2 行的网格有 4 条列线和 3 条行线:

    1     2     3     4    ← 列线编号
    │     │     │     │
1 ──┌─────┬─────┬─────┐
    │     │     │     │
2 ──├─────┼─────┼─────┤
    │     │     │     │
3 ──└─────┴─────┴─────┘

行线编号

从 1 开始是因为网格线定义的是边界,第 1 条线是网格的起始边界。也可以用负数从末尾计数:-1 是最后一条线,-2 是倒数第二条。

grid-column: 1 / -1 表示"从第一条线到最后一条线",即横跨整行——这是 Grid 中最常用的"全宽"写法。

题目四:auto-fill 和 auto-fit 在什么情况下表现不同?

只有当项目数量不足以填满一行时才有区别。

css
.container {
  width: 600px;
  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
/* 假设只有 2 个项目 */

auto-fill:创建尽可能多的列(6列),即使有空列。项目按 minmax 规则,每列 100px,空列也占 100px。

auto-fit:同样计算出 6 列,但将空列折叠为 0px。由于 max 是 1fr,多余空间分配给有内容的列,2 个项目各占 300px。

实际开发:大多数场景使用 auto-fit,因为你通常希望项目填满可用空间。auto-fill 适用于需要保持固定列宽、不希望项目拉伸的场景。

题目五:grid-template-areas 有哪些限制?

  1. 命名区域必须是矩形——不能是 L 形、T 形等非矩形区域
  2. 所有行的列数必须相同
  3. 区域名称不能重复用于不连续的位置
css
/* ❌ 错误:header 区域不是矩形 */
grid-template-areas:
  "header header ."
  "header sidebar content";

/* ✅ 正确 */
grid-template-areas:
  "header  header  header"
  "sidebar content content";

追问思考

  • 为什么说 Grid 是"布局驱动"而 Flex 是"内容驱动"?能举一个实际场景说明吗?
  • grid-template-columns: 1fr 1fr 1frgrid-template-columns: repeat(3, 1fr) 有没有区别?
  • 当容器宽度无法整除列数时,fr 单位的计算会不会有精度问题?浏览器如何处理?
  • Subgrid 解决了什么实际问题?没有 Subgrid 之前是怎么处理嵌套网格对齐的?
  • grid-auto-flow: dense 在什么业务场景下适合使用?它的无障碍风险是什么?

用心学习,用代码说话 💻