跳到主要内容

CSS Grid 布局全面指南

很多同学第一次接触 Grid 时,会觉得它“语法很多、名字很像、看起来很强但不太敢用”。

这很正常,因为 CSS Grid 不是某一个单独属性,而是一整套二维布局系统。它把“页面怎么分行、怎么分列、元素放到哪一格、空白怎么分配、内容怎么对齐”这些问题统一交给浏览器处理。

如果你把 Flex 理解成“排一条线”,那 Grid 更像是“先画出棋盘,再把内容放进去”。

本文会把 CSS Grid 中最核心的属性、单位、函数、关键字、概念和实战写法一次讲完整,并尽量用通俗语言解释清楚。

为了方便你建立直觉,下面的关键代码示例后面都补了“渲染结果”预览,你可以边看代码边对照效果。


一、先记住一句话:Grid 是二维布局

Flexbox 很擅长处理一维布局

  • 要么主要控制一行
  • 要么主要控制一列
  • 重点在“主轴 + 交叉轴”上的分配

Grid 则擅长处理二维布局

  • 同时定义行和列
  • 同时决定元素的横向和纵向位置
  • 更适合页面骨架、卡片墙、仪表盘、后台布局、复杂响应式布局
布局方式更擅长什么典型场景
Flexbox一维排列导航栏、按钮组、单行卡片、居中对齐
Grid二维布局页面主结构、卡片矩阵、看板、图文混排

最简单的开启方式:

.container {
display: grid;
}

或者:

.badge-list {
display: inline-grid;
}

两者区别是:

  • display: grid:容器在外部表现为块级盒子
  • display: inline-grid:容器在外部表现为行内级盒子
术语补充

当元素设置为 display: griddisplay: inline-grid 时,它会创建 Grid Formatting Context(GFC,网格格式化上下文)。你可以简单理解为:这个容器内部开始按照 Grid 规则排版,而不是普通文档流规则。


二、Grid 的核心概念

学 Grid,第一步不是背属性,而是先把这些概念分清。

概念英文你可以怎么理解
网格容器Grid Container开启了 display: grid 的父元素
网格子项Grid Item网格容器的直接子元素
网格线Grid Line每一列、每一行的边界线
轨道Grid Track两条相邻网格线之间的空间;一列或一行就是一个轨道
单元格Grid Cell一行和一列交叉形成的最小格子
区域Grid Area多个单元格组成的矩形区域
显式网格Explicit Grid你通过 grid-template-* 明确定义出来的网格
隐式网格Implicit Grid子项超出显式网格后,浏览器自动补出来的网格

2.1 行、列、线编号怎么理解?

看这个例子:

.layout {
display: grid;
grid-template-columns: 200px 1fr 1fr;
grid-template-rows: 80px auto;
}

它表示:

  • 一共有 3 列轨道
  • 一共有 2 行轨道
  • 因为“轨道数 = 线之间的空间”,所以:
    • 3 列会有 4 条列线
    • 2 行会有 3 条行线

因此你可以这样放元素:

.sidebar {
grid-column: 1 / 2;
grid-row: 1 / 3;
}

.main {
grid-column: 2 / 4;
grid-row: 2 / 3;
}

其中:

  • 1 / 2 表示从第 1 条线到第 2 条线
  • 2 / 4 表示从第 2 条线跨到第 4 条线
  • -1 表示最后一条线

所以这个写法很常见:

.full-width {
grid-column: 1 / -1;
}

它的意思是:从第一条线铺到最后一条线,也就是整行占满。

2.2 justify-*align-* 别机械地背成“水平/垂直”

在大多数中文网站里,你可以暂时粗略理解为:

  • justify-*:更接近水平方向
  • align-*:更接近垂直方向

但更准确的说法是:

  • justify-* 控制 inline 轴
  • align-* 控制 block 轴

这也是为什么在不同书写模式下,它们不一定永远对应“左/右”和“上/下”。


三、从一个最小可用示例开始

先看一段最基础的 Grid 代码:

<div class="cards">
<article>卡片 1</article>
<article>卡片 2</article>
<article>卡片 3</article>
<article>卡片 4</article>
<article>卡片 5</article>
<article>卡片 6</article>
</div>
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}

这段代码做了三件事:

  1. display: grid:告诉浏览器,这个容器内部用 Grid 排版
  2. grid-template-columns: repeat(3, 1fr):定义 3 列,每列平分剩余空间
  3. gap: 16px:定义网格项之间的间距

效果可以理解成:

  • 第一行:卡片 1、2、3
  • 第二行:卡片 4、5、6
  • 行高默认由内容撑开

渲染结果:

卡片 1
卡片 2
卡片 3
卡片 4
卡片 5
卡片 6

如果你把列数改成:

grid-template-columns: 240px 1fr 1fr;

就表示:

  • 第一列固定 240px
  • 后两列平分剩余空间

这就是 Grid 最重要的思维方式:先定义轨道,再让元素进入轨道。


四、容器属性:先把网格骨架搭出来

下面这张表,先把 Grid 容器常用属性总览一遍:

属性作用常见值
display开启 Gridgridinline-grid
grid-template-columns定义列轨道1fr 1frrepeat(3, 1fr)
grid-template-rows定义行轨道80px auto 1fr
grid-template-areas用命名区域描述布局"header header"
grid-template上面三者的简写"header header" 64px / 220px 1fr
column-gap列间距16px
row-gap行间距24px
gap行列间距简写16px 24px
grid-auto-columns隐式列尺寸160pxminmax(0, 1fr)
grid-auto-rows隐式行尺寸120pxauto
grid-auto-flow自动放置方向rowcolumndense
justify-items每个子项在单元格内的 inline 轴对齐startcenterstretch
align-items每个子项在单元格内的 block 轴对齐startendstretch
place-itemsalign-items + justify-items 简写center stretch
justify-content整个网格轨道在容器中的 inline 轴分布centerspace-between
align-content整个网格轨道在容器中的 block 轴分布startspace-evenly
place-contentalign-content + justify-content 简写center space-between
grid多个 Grid 长属性的总简写auto-flow dense / repeat(4, 1fr)

4.1 display: griddisplay: inline-grid

.page {
display: grid;
}

.tags {
display: inline-grid;
grid-auto-flow: column;
gap: 8px;
}
  • grid:外部像块元素,会独占一行
  • inline-grid:外部像行内元素,可以和文字、其他行内内容并排

4.2 grid-template-columns

它用来定义每一列多宽

.layout {
display: grid;
grid-template-columns: 220px 1fr 1fr;
}

上面表示:

  • 第一列固定 220px
  • 后两列各占 1fr

你还能混合更多写法:

grid-template-columns: 12rem minmax(0, 1fr) fit-content(20rem);
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(2, minmax(180px, 1fr));
grid-template-columns: [sidebar-start] 240px [sidebar-end content-start] 1fr [content-end];
grid-template-columns: subgrid;

这里出现了很多 Grid 中最常见的单位和函数:

  • fr:剩余空间分配单位
  • repeat():重复轨道定义
  • minmax():设置最小值和最大值范围
  • fit-content():在不超过上限的前提下尽量贴合内容
  • []:给网格线命名
  • subgrid:继承父网格的轨道

4.3 grid-template-rows

它用来定义每一行多高

.page {
display: grid;
min-height: 100vh;
grid-template-rows: 64px 1fr auto;
}

表示:

  • 第一行固定 64px(顶部导航)
  • 第二行吃掉剩余空间(主内容区)
  • 第三行由内容高度决定(底部)
百分比行高有前提

如果你在 grid-template-rows 里写百分比,例如 50% 50%,那容器本身通常需要有一个明确高度,否则结果往往不如预期。

4.4 grid-template-areas

这是 Grid 最适合初学者、也最适合读代码的写法之一。

<div class="page">
<header class="header">头部</header>
<aside class="sidebar">侧栏</aside>
<main class="main">主内容</main>
<footer class="footer">底部</footer>
</div>
.page {
display: grid;
grid-template-columns: 220px minmax(0, 1fr);
grid-template-rows: 64px 1fr auto;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
min-height: 100vh;
gap: 16px;
}

.header {
grid-area: header;
}

.sidebar {
grid-area: sidebar;
}

.main {
grid-area: main;
}

.footer {
grid-area: footer;
}

渲染结果:

头部

侧栏

主内容

底部

这段代码最大的优点是:布局一眼就能看出来。

使用 grid-template-areas 时要记住几条规则:

  1. 每一行字符串里的列数必须一致
  2. . 表示空白单元格
  3. 同名区域必须组成矩形,不能拐弯
  4. 给元素赋值时使用 grid-area: 名称

比如:

grid-template-areas:
"cover title"
"cover meta";
一个隐藏知识点

当你声明了 grid-template-areas 后,浏览器会自动生成对应的命名线,例如 header-startheader-end。这让你后面可以在 grid-columngrid-row 中继续引用这些名字。

4.5 grid-template

这是下面几个属性的简写:

  • grid-template-rows
  • grid-template-columns
  • grid-template-areas

例如:

.page {
display: grid;
grid-template:
"header header" 64px
"sidebar main" 1fr
"footer footer" auto
/ 220px minmax(0, 1fr);
}

它等价于分别设置:

  • 三行:64px1frauto
  • 两列:220pxminmax(0, 1fr)
  • 区域:header / sidebar / main / footer

如果你觉得这个简写太难读,完全可以优先使用长属性。实战中,可读性往往比“写得短”更重要。

4.6 gaprow-gapcolumn-gap

它们用于定义网格项之间的间距。

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

这相当于:

row-gap: 16px;
column-gap: 24px;

你还会在旧代码里看到:

  • grid-gap
  • grid-row-gap
  • grid-column-gap

现在更推荐统一使用:

  • gap
  • row-gap
  • column-gap

4.7 grid-auto-rowsgrid-auto-columns

当子项超出了你定义的显式网格,浏览器会自动创建隐式轨道

这时候,这两个属性就负责定义这些“自动补出来的轨道”的尺寸。

.gallery {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 120px;
}

如果某个元素被放到了第 5 行,而你只显式定义了前 2 行,那么第 3、4、5 行就是隐式行,它们会使用 grid-auto-rows: 120px

同理:

grid-auto-columns: minmax(160px, 1fr);

表示隐式列的宽度规则。

4.8 grid-auto-flow

它决定没有显式指定位置的元素如何自动排入网格。

.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-flow: row;
}

常见值:

含义
row默认值,按行依次填充
column按列依次填充
dense尽量回填空洞
row dense按行填充,并尝试回填前面的空位
column dense按列填充,并尝试回填前面的空位

例如:

.masonry-like {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 80px;
grid-auto-flow: row dense;
gap: 12px;
}

dense 常用于“图片墙补洞”这类场景,但它可能让视觉顺序DOM 顺序不完全一致,因此要谨慎使用在有明显阅读顺序的内容里。

4.9 justify-itemsalign-itemsplace-items

这组属性控制的是:每个子项在自己的单元格内部怎么对齐

.board {
display: grid;
grid-template-columns: repeat(3, 160px);
grid-auto-rows: 120px;
justify-items: center;
align-items: center;
}

常见值:

  • start
  • end
  • center
  • stretch(默认值)

其中:

  • justify-items:控制 inline 轴对齐
  • align-items:控制 block 轴对齐
  • place-items:前者 + 后者的简写

例如:

place-items: center stretch;

相当于:

align-items: center;
justify-items: stretch;

渲染结果:

1
2
3
4
5
6

4.10 justify-contentalign-contentplace-content

这组属性经常和 justify-items / align-items 混淆。

它们控制的不是“单个子项”,而是:整个网格轨道作为一个整体,在容器里怎么分布

.wrapper {
display: grid;
width: 900px;
height: 500px;
grid-template-columns: repeat(3, 160px);
grid-template-rows: repeat(2, 120px);
justify-content: center;
align-content: space-between;
}

上面之所以能看出效果,是因为:

  • 容器宽 900px
  • 轨道总宽不到 900px
  • 容器高 500px
  • 轨道总高不到 500px

这时才存在“多余空间如何分配”的问题。

常见值:

  • start
  • end
  • center
  • stretch
  • space-between
  • space-around
  • space-evenly

place-content 是:

place-content: center space-between;

相当于:

align-content: center;
justify-content: space-between;

4.11 grid 总简写

grid 是一个很强但也很容易写晕的简写属性。

例如:

.gallery {
display: grid;
grid: auto-flow dense 120px / repeat(4, 1fr);
}

它可以同时设置:

  • grid-auto-flow
  • grid-auto-rows
  • grid-template-columns

如果你在团队开发里发现大家不容易看懂,建议优先拆开写。


五、子项属性:把元素放到正确的位置

Grid 子项属性的核心目标只有一个:决定元素占哪几列、哪几行,以及在格子里怎么对齐。

属性作用典型写法
grid-column-start指定列起始线1sidebar-startspan 2
grid-column-end指定列结束线3-1span 3
grid-row-start指定行起始线1header-start
grid-row-end指定行结束线4span 2
grid-column列起止简写1 / 32 / span 2
grid-row行起止简写1 / 4auto / span 2
grid-area区域名称或四值简写main1 / 2 / 3 / 4
justify-self当前子项在单元格内的 inline 轴对齐startcenter
align-self当前子项在单元格内的 block 轴对齐endstretch
place-selfalign-self + justify-self 简写center stretch
order调整视觉顺序2-1
z-index控制重叠层级110

5.1 grid-column-start / grid-column-end

.hero {
grid-column-start: 1;
grid-column-end: 3;
}

等价于:

.hero {
grid-column: 1 / 3;
}

你可以写的值包括:

  • 数字线号:12-1
  • 命名线:content-start
  • 跨度:span 2
  • auto

例如:

.banner {
grid-column: 1 / -1;
}

.card-large {
grid-column: 2 / span 2;
}

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

5.2 grid-row-start / grid-row-end

它们的规则和列完全一样,只不过变成纵向。

.promo {
grid-row-start: 1;
grid-row-end: 3;
}

等价于:

.promo {
grid-row: 1 / 3;
}

再看一个常见写法:

.card-tall {
grid-row: auto / span 2;
}

意思是:起始行由自动放置算法决定,但纵向跨两行。

5.3 grid-columngrid-row

这是最常用的简写形式。

.item-a {
grid-column: 1 / 3;
grid-row: 1 / 2;
}

.item-b {
grid-column: 3 / 4;
grid-row: 1 / 3;
}

你也可以和命名线混合:

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

渲染结果:

A

B

C

D

E

5.4 grid-area

grid-area 有两种非常常见的用法。

第一种:给命名区域赋值

.main {
grid-area: main;
}

它通常配合 grid-template-areas 使用。

第二种:一次写完四条线

语法是:

grid-area: row-start / column-start / row-end / column-end;

例如:

.feature {
grid-area: 1 / 2 / 3 / 4;
}

意思是:

  • 从第 1 条行线开始
  • 从第 2 条列线开始
  • 到第 3 条行线结束
  • 到第 4 条列线结束

5.5 justify-selfalign-selfplace-self

如果容器上统一设置了:

.board {
display: grid;
justify-items: stretch;
align-items: stretch;
}

某个子项想“单独例外”,就可以用 *-self 覆盖:

.avatar {
justify-self: center;
align-self: start;
}

或者:

.avatar {
place-self: start center;
}

相当于:

align-self: start;
justify-self: center;

5.6 order

order 不是 Grid 专属属性,但它也适用于 Grid 子项。

.important {
order: -1;
}

作用是改变视觉排列顺序

但一定要注意:

  • order 改变的是视觉顺序
  • DOM 顺序、阅读顺序、Tab 顺序不一定同步改变

所以:

  • 做少量视觉微调可以
  • 不要用它彻底打乱信息语义顺序

5.7 z-index 与重叠

Grid 子项是可以重叠的,只要它们被放到了同一片区域,或者跨行跨列发生覆盖。

.chart {
grid-column: 1 / 4;
grid-row: 1 / 3;
}

.floating-panel {
grid-column: 3 / 5;
grid-row: 1 / 2;
z-index: 2;
}

这里有一个容易被忽视的细节:

Grid 子项可以直接使用 z-index 控制层级,即使没有显式写 position

这点和普通块元素不完全一样。


六、Grid 中最重要的单位、函数与关键字

很多同学真正卡住 Grid 的地方,不是属性名,而是这些值:frrepeat()minmax()auto-fit……

下面逐个讲透。

6.1 fr:剩余空间分配单位

grid-template-columns: 1fr 2fr 1fr;

表示把剩余空间分成 4 份,三列分别拿 1、2、1 份。

注意,不是把容器总宽直接硬切成 1:2:1,而是先扣除:

  • 固定宽轨道
  • gap
  • 某些最小内容约束

剩下的部分才交给 fr 分配。

例如:

grid-template-columns: 240px 1fr 1fr;

表示:

  1. 先拿出 240px 给第一列
  2. 再减去列间距
  3. 剩下空间后两列平分

渲染结果:

1fr
2fr
1fr
高频实战写法:minmax(0, 1fr)

在一些实际布局里,单纯写 1fr 可能因为子项的最小内容尺寸过大而撑爆容器。为了更稳,很多时候会写成:

grid-template-columns: 240px minmax(0, 1fr);

这相当于告诉浏览器:这一列最窄可以压到 0,最大再去吃剩余空间。

6.2 repeat():重复定义轨道

grid-template-columns: repeat(4, 1fr);

等价于:

grid-template-columns: 1fr 1fr 1fr 1fr;

它的好处是简洁、可读,并且可以和别的函数组合:

grid-template-columns: repeat(3, minmax(180px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));

6.3 minmax():给轨道一个区间

grid-template-columns: minmax(200px, 1fr) 2fr;

意思是第一列:

  • 最小 200px
  • 最大可以长到 1fr

它特别适合这几类场景:

  • 响应式卡片列
  • 侧栏最小宽度保护
  • 防止列在窄屏下压得过小

最常见组合:

repeat(auto-fit, minmax(240px, 1fr))

6.4 fit-content():内容导向,但有上限

grid-template-columns: 220px fit-content(320px) 1fr;

你可以把它理解成:

  • 这一列尽量根据内容大小决定
  • 但不要超过我给的上限

它很适合:

  • 标签列
  • 时间轴时间列
  • 内容长度差异较大的描述区

6.5 min-contentmax-content

这两个叫内在尺寸关键字(intrinsic sizing keywords)

grid-template-columns: min-content 1fr max-content;

简单理解:

  • min-content:在不溢出的前提下,尽可能窄
  • max-content:内容完全展开时所需的理想宽度

它们在“内容驱动布局”里很有用,但如果你还不熟,可以先重点掌握 frrepeat()minmax()

6.6 auto

auto 的含义经常要结合上下文理解。

例如:

grid-template-columns: 200px auto 1fr;

这里中间列表示:尺寸由内容和可用空间共同参与决定。

在放置属性中:

grid-row: auto / span 2;

表示起始位置交给自动放置算法决定。

6.7 auto-fillauto-fit

它们常和 repeat() + minmax() 一起出现:

grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));

二者都能让列数随容器宽度自动变化,但细节不同:

  • auto-fill:尽量“预留轨道”,哪怕有些轨道最后是空的
  • auto-fit:会把空轨道折叠掉,让已有项目把空间吃满

如果你只是想做常见的“自适应卡片列表”,多数情况下优先从:

repeat(auto-fit, minmax(240px, 1fr))

开始就够了。

6.8 span

它表示“跨几个轨道”。

.card-large {
grid-column: 2 / span 2;
grid-row: auto / span 2;
}

含义是:

  • 从第 2 条列线开始,横向跨 2 列
  • 行起点自动决定,纵向跨 2 行

6.9 命名线 []

除了用数字线号,你还可以给网格线起名字。

.layout {
display: grid;
grid-template-columns:
[sidebar-start] 220px
[sidebar-end content-start] minmax(0, 1fr)
[content-end];
}

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

它的优点是:当布局变复杂后,语义名字往往比单纯写 1 / 3 更容易维护。

6.10 subgrid

subgrid 是 Grid 中非常值得了解的进阶能力。

它允许嵌套网格继承父网格的轨道,从而让多个子组件在列线上严格对齐。

.page {
display: grid;
grid-template-columns: 160px 1fr;
gap: 12px 24px;
}

.card-list {
grid-column: 1 / -1;
display: grid;
grid-template-columns: subgrid;
}

当你需要:

  • 卡片标题列对齐
  • 多块信息区共享列宽
  • 嵌套组件仍然和页面主网格对齐

subgrid 就很有价值。

6.11 calc()min()max()clamp()

Grid 轨道值也可以结合通用 CSS 数学函数使用。

grid-template-columns: 220px minmax(0, calc(100% - 220px));
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
grid-auto-rows: clamp(80px, 12vw, 160px);

它们的价值在于:

  • calc():混合不同单位计算
  • min():取较小值
  • max():取较大值
  • clamp():设置最小值、理想值、最大值

七、自动放置算法:浏览器到底怎么“找空位”

如果你不给所有子项都手动写 grid-column / grid-row,浏览器会启动自动放置算法(auto-placement algorithm)

你可以把它理解成:浏览器拿着一个游标,按规则依次给元素找坑位。

7.1 默认规则

默认情况下:

grid-auto-flow: row;

意思是浏览器优先沿着“行方向”把元素依次往后填。

7.2 dense 为什么能“补洞”?

看这个场景:

  • 某个元素跨了 2 列
  • 导致前面留下一个 1 列的小空位
  • 后面的普通小卡片本来排不到那个位置

如果你开启:

grid-auto-flow: row dense;

浏览器会尝试回头把能塞进去的小元素补进这个洞里。

优点:

  • 空间利用率更高
  • 图片墙更紧凑

风险:

  • 视觉顺序可能和 DOM 顺序不同
  • 不适合文章流、表单流、严格阅读顺序内容

八、响应式 Grid 实战模式

8.1 自适应卡片墙:最常用的一种写法

.card-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 16px;
}

这句可以直接背下来,它几乎是 Grid 响应式布局里最常见的黄金写法。

它的含义是:

  • 每张卡片最小 240px
  • 放得下几列就放几列
  • 剩余空间自动均分

非常适合:

  • 文章卡片列表
  • 商品列表
  • 演示案例网格

渲染结果:

文章卡片 A
文章卡片 B
文章卡片 C
文章卡片 D
文章卡片 E

8.2 页面骨架布局

.dashboard {
display: grid;
min-height: 100vh;
grid-template:
"header header" 64px
"sidebar main" 1fr
/ 240px minmax(0, 1fr);
}

.dashboard__header {
grid-area: header;
}

.dashboard__sidebar {
grid-area: sidebar;
}

.dashboard__main {
grid-area: main;
}

@media (max-width: 960px) {
.dashboard {
grid-template:
"header" 64px
"main" 1fr
/ minmax(0, 1fr);
}

.dashboard__sidebar {
display: none;
}
}

这种写法的优势是:

  • 结构清晰
  • 响应式改造直观
  • 读 CSS 就能看出布局变化

8.3 12 列栅格系统

很多设计稿喜欢用 12 列网格,你也可以直接用 Grid 实现:

.row {
display: grid;
grid-template-columns: repeat(12, minmax(0, 1fr));
gap: 16px;
}

.col-3 {
grid-column: span 3;
}

.col-4 {
grid-column: span 4;
}

.col-6 {
grid-column: span 6;
}

.col-12 {
grid-column: 1 / -1;
}

这比传统浮动栅格更自然,也更容易做响应式覆盖。

渲染结果:

span 3

span 6

span 3

全宽 span 12

8.4 嵌套布局与 subgrid

.article-list {
display: grid;
grid-template-columns: 120px 1fr;
gap: 12px 20px;
}

.article-card {
grid-column: 1 / -1;
display: grid;
grid-template-columns: subgrid;
align-items: start;
}

这样每张卡片内部的“缩略图列”和“正文列”就能严格继承父级列宽,不会一张卡片一个样。


九、Grid 和 Flex 到底怎么选?

这也是面试和实战里都非常高频的问题。

问题更推荐谁原因
一行按钮左右排开Flexbox一维分布更直接
页面主结构有行有列Grid二维定义更清晰
卡片自适应换列Gridrepeat(auto-fit, minmax()) 很强
单个卡片内部图标和文字对齐Flexbox组件内的一维对齐更轻量
后台仪表盘、多区域面板Grid更适合区域化布局

一个非常实用的经验是:

页面大骨架用 Grid,组件内部小排列用 Flex。

例如:

  • 整个页面:Grid
  • 卡片内部标题 + 按钮:Flex

这两者不是对立关系,而是经常一起使用。


十、常见坑与排错清单

10.1 为什么写了 justify-content 没效果?

因为它控制的是整个网格轨道在容器中的分布

如果你的轨道本来就已经把容器撑满了,就没有“多余空间”可以分配,自然看不出效果。

这时你可能真正想要的是:

  • justify-items
  • align-items
  • justify-self
  • align-self

10.2 为什么 1fr 有时会把内容撑爆?

很多同学以为 1fr 就一定会乖乖收缩,其实不一定。

如果子项里有:

  • 超长单词
  • 不换行文本
  • 超宽图片
  • white-space: nowrap

那轨道的最小内容尺寸可能会阻止它继续收缩。

常见解法:

grid-template-columns: 240px minmax(0, 1fr);

.content {
min-width: 0;
}

10.3 为什么 grid-template-areas 报错或不生效?

检查这几项:

  1. 每行列数是否一致
  2. 同名区域是否是矩形
  3. 元素上的 grid-area 名称是否拼写一致
  4. 是否把 . 写成了别的字符

10.4 auto-fitauto-fill 总是分不清怎么办?

可以先记一个够用版本:

  • 想让现有卡片尽量把空间铺满:auto-fit
  • 想保留“潜在轨道”的占位概念:auto-fill

日常卡片列表,大多数时候先用 auto-fit

10.5 为什么图片墙用了 dense 后顺序怪怪的?

因为 dense 会为了补洞而尝试回填前面的空位。

所以:

  • 做纯视觉瀑布感布局可以考虑
  • 做新闻流、步骤流、表单流要谨慎

10.6 为什么明明定义了两行,后面元素还在往下长?

因为浏览器帮你创建了隐式网格

这时要检查:

  • 是否有元素被放到了更靠后的行列
  • 是否设置了 grid-auto-rows / grid-auto-columns

10.7 为什么百分比行高看起来不对?

因为百分比高度往往依赖父容器有确定高度。没有明确高度时,百分比行高很容易“不按你脑补的方式工作”。

10.8 为什么视觉上能换顺序,但无障碍仍然有问题?

因为 Grid 改变的是视觉布局,而不是 DOM 本身的语义顺序。

所以:

  • 阅读顺序敏感的内容,优先保证 DOM 顺序正确
  • 不要只靠 Grid 把正文“视觉上挪来挪去”

10.9 实战排错时看什么工具?

浏览器 DevTools 的 Grid Overlay 非常有用。排查时重点看:

  • 网格线编号
  • 显式轨道和隐式轨道
  • 区域名称
  • gap 是否如预期
  • 子项到底跨了几行几列

十一、面试高频问答

Q1:Grid 和 Flex 的核心区别是什么?

参考答案:

Flex 更擅长一维布局,主要解决一行或一列里的分配与对齐;Grid 更擅长二维布局,可以同时定义行和列,适合页面骨架、卡片矩阵、后台布局等复杂场景。实战里常见做法是:大布局用 Grid,小组件内部用 Flex。

Q2:1fr 到底是什么意思?

参考答案:

fr 是剩余空间分配单位。浏览器会先扣除固定尺寸轨道、间距以及部分最小内容约束,再把剩余空间按 fr 比例分给对应轨道。所以 1fr 2fr 1fr 不是简单地把总宽度切成 1:2:1,而是把剩余空间切成 1:2:1。

Q3:auto-fitauto-fill 的区别是什么?

参考答案:

两者都常用于 repeat(auto-xxx, minmax(...))auto-fill 更倾向于保留可放置的轨道,即使有些轨道最后为空;auto-fit 会把空轨道折叠,让已有项目尽量铺满空间。做常规响应式卡片列表时,通常优先使用 auto-fit

Q4:为什么很多人写 minmax(0, 1fr),而不是只写 1fr

参考答案:

因为 Grid 子项可能受最小内容尺寸影响,导致单纯的 1fr 轨道无法继续收缩,最终把容器撑爆。minmax(0, 1fr) 明确告诉浏览器:这条轨道最小可以压到 0,最大再按剩余空间分配,从而更稳地处理超长内容。

Q5:显式网格和隐式网格有什么区别?

参考答案:

显式网格是开发者通过 grid-template-columnsgrid-template-rowsgrid-template-areas 明确定义出来的网格。隐式网格则是当子项超出显式范围时,浏览器自动创建的额外行列。隐式网格的尺寸由 grid-auto-rowsgrid-auto-columns 控制。

Q6:grid-template-areas 有什么限制?

参考答案:

它要求每一行列数一致,同名区域必须组成矩形,不能出现 L 形、T 形等不规则区域。优点是语义化强、可读性高,非常适合页面结构布局。

Q7:justify-items / align-itemsjustify-content / align-content 的区别是什么?

参考答案:

justify-itemsalign-items 控制的是每个子项在自己单元格内部如何对齐;justify-contentalign-content 控制的是整个网格轨道作为整体在容器中如何分布。前者管“格子里的内容”,后者管“整张网格在容器里的位置”。

Q8:什么时候适合使用 grid-auto-flow: dense

参考答案:

当你更关注视觉紧凑度,而不是严格的视觉顺序时,可以考虑 dense,例如图片墙、卡片拼贴布局。它会尝试回填前面的空洞。但如果页面内容有明确阅读顺序,就要谨慎,因为 dense 可能导致视觉顺序和 DOM 顺序不一致。

Q9:grid-column: 1 / -1 为什么这么常见?

参考答案:

因为 -1 代表最后一条网格线,所以 grid-column: 1 / -1 的意思是从第一条列线铺到最后一条列线,也就是横向占满整行。这在横幅、分隔区、全宽模块中非常常见。

Q10:subgrid 解决了什么问题?

参考答案:

subgrid 解决的是嵌套布局里“子组件和父组件列线对不齐”的问题。它让子网格可以直接继承父网格的轨道定义,从而让多层组件共享同一套列宽和对齐规则,特别适合复杂列表、表格式卡片、信息面板等场景。


十二、总结

如果你刚学完这篇文章,至少应该记住这 6 句话:

  1. Grid 是二维布局,先定行列,再放元素。
  2. 容器属性负责画棋盘,子项属性负责落子。
  3. frrepeat()minmax() 是 Grid 最核心的尺寸组合。
  4. 响应式卡片墙优先记住 repeat(auto-fit, minmax(240px, 1fr))
  5. justify-* / align-* 要分清是管“子项”还是管“整张网格”。
  6. 复杂页面骨架优先考虑 Grid,组件内部细节排版继续交给 Flex。

如果你愿意继续深入,下一个非常值得配套学习的主题是:

  • Flexbox
  • BFC / IFC / GFC
  • CSS 单位与 min() / max() / clamp()
  • 响应式设计与媒体查询

把这些知识串起来,你对 CSS 布局的整体理解会完整很多。