Menu

CSS Grid 网格布局:行列、fr、gap 与区域

CSS grid 同时按行和列排列元素。本文讲解 display: grid、grid-template-columns、fr 单位、repeat() 和 minmax()、auto-fill 与 auto-fit 的区别、用 grid-column 放置元素,以及 grid-template-areas,并附一个可以编辑和测量的实时网格。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

三行代码搭一个网格

在容器上设置 display: grid,再用 grid-template-columns 列出它的列。子元素按顺序填入单元格(在英文中从左到右),每当列被填满就开始新的一行:

七个元素、三列:两整行,第三行的第一列放元素 7。每列宽度相同,同一行的每个元素高度相同,所以边缘在两个方向上都对齐。这正是 grid 比 flexbox 多出来的能力。

Grid 在线演练

输入任意 grid-template-columns 值,或点击一个预设。读数显示浏览器算出的列宽(来自 getComputedStyle),以及这些元素占用了多少行:

可以注意这几点:1fr 2fr 让第二列正好是第一列的两倍宽。200px 1fr 保留 200px,其余都给第二列。auto 1fr auto 让两侧的列按内容决定宽度。repeat(4, 100px) 会在末尾留下空白,因为没有任何一列是弹性的。无效的值(比如 1fr 2)会被忽略,网格保持上一个有效的值。

fr 单位

fr 的意思是“剩余空间中的一份”。浏览器先扣除固定宽度的列(px、rem、%、auto 内容)和间距,再按数字把剩下的部分分给各个 fr 列。

600px 容器中的 grid-template-columns,无间距列宽
1fr 1fr 1fr200, 200, 200
1fr 2fr200, 400
100px 1fr 1fr100, 250, 250
1fr 50%300, 300

fr 和 % 不一样:百分比加上间距可能超过 100% 而溢出,而 fr 列始终只分配扣除间距之后剩下的空间。

repeat()、minmax() 与不用媒体查询的响应式网格

repeat(4, 1fr) 就是 1fr 1fr 1fr 1fr。minmax(120px, 1fr) 是一个永远不窄于 120px、并且会增长去占一份空间的列。把它们和 auto-fill 或 auto-fit(代替具体数量)组合起来,网格就会创建尽可能多的能放下的列:

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

这一条声明就能得到容器能容纳的、每列至少 180px 的若干列:宽屏上好几列,手机上一列,完全不需要媒体查询。当元素比列少时,auto-fill 和 auto-fit 的区别就显现出来了:

容器能放下五个 100px 的列。auto-fill 保留全部五列,所以 A、B、C 各 112px,剩下两个空列。auto-fit 折叠了空列,所以三个元素各拉伸到大约 187px。

放置元素:grid-column 与 grid-row

网格线从起始边开始编号,从 1 开始。grid-column: 1 / 3 表示“从第 1 条线到第 3 条线”,也就是两列。span 2 表示“从所在位置起跨两列”,-1 是最后一条线:

深色元素占满整个第一行。蓝色元素覆盖一个 2 × 2 的区块,琥珀色元素跨两行。元素 a 和 b 会自己填入剩下的单元格:grid 会把每个能放的元素放进下一个空闲单元格。grid-auto-rows: 60px 设置那些没有用 grid-template-rows 声明的行的高度。

grid-template-areas:给布局的各部分命名

做页面布局时,给各区域命名,再用文字把它们“画”出来。每个字符串是一行,每个单词是一个单元格,元素用 grid-area 加入某个区域:

页头和页脚横跨两列,因为它们的名称填满了所在行的两个单元格。中间一行是 1fr,所以它占据 280px 中剩下的高度。要在手机上把所有内容堆叠起来,媒体查询只需把区域重新画成一列:grid-template-areas: "header" "side" "main" "footer" 加上 grid-template-columns: 1fr。媒体查询一页展示了这种写法。

在单元格内对齐

网格元素默认会拉伸填满所在的单元格。下面这些属性用来在单元格内移动它们:

属性用在移动
justify-items容器每个元素沿行方向移动(行内轴)
align-items容器每个元素上下移动(块轴)
place-items容器简写:place-items: center 在两个方向上都居中
justify-self / align-self元素单个元素
justify-content / align-content容器当网格比容器小时,移动整组轨道

display: grid; place-items: center; 是在盒子中居中一个子元素最短的写法。

grid 还是 flexbox

grid 是二维的:你决定列(和行),内容去填充它们,所以画廊里的每张卡片宽度都相同,最后一行也是如此。flexbox 是一维的:元素沿一条线根据内容决定自身大小。两者可以很好地搭配:页面和卡片画廊用 grid,每张卡片内部的标题行用 flexbox。另一半内容见 flexbox 一页。

顺序与无障碍

grid-template-areas、grid-row、grid-column 和 order 可以把元素放在任何位置,但屏幕阅读器和 Tab 键仍然按照 HTML 的顺序来。让源码顺序保持有意义(页头、导航、主体、页脚),用 grid 来排布它,而不是用来弥补一个毫无道理的 HTML 顺序。

常见错误

  • 把 display: grid 写在子元素上。 它应该写在父元素上;只有直接子元素才是网格元素。
  • 百分比和间距一起用。 grid-template-columns: 50% 50% 加上 gap: 16px 会溢出 16px。请用 1fr 1fr。
  • 想要 auto-fit 却用了 auto-fill。 元素少时,auto-fill 会留下空列。换成 auto-fit 让元素拉伸。
  • 1fr 列超出了它应得的份额。 1fr 列不会缩到比它最长的内容(一个长 URL、一张宽图片)还窄。用 minmax(0, 1fr) 让它可以收缩。
  • 区域字符串不是矩形。 每个命名区域都必须构成一个矩形,每行字符串的单元格数量也必须相同,否则整条 grid-template-areas 声明都会被忽略。
  • 忘了网格线从 1 开始编号。 三列有四条线:1 / 4 或 1 / -1 跨越全部三列。

常见问题

CSS grid 是什么?

一种同时按行和列放置元素的布局系统。你在容器上设置 display: grid,用 grid-template-columns 描述它的列(也可以描述行),它的直接子元素就会按顺序填入单元格。

CSS grid 中的 1fr 是什么意思?

fr 是网格容器中剩余空间的一份。grid-template-columns: 1fr 1fr 1fr 得到三个等宽的列,1fr 2fr 让第二列的宽度是第一列的两倍。固定尺寸和间距先被扣除,fr 列再平分剩下的部分。

auto-fill 和 auto-fit 有什么区别?

两者都会创建尽可能多的能放下的列。auto-fill 会保留空列,所以元素少时它们保持较窄,行尾留出空白。auto-fit 会折叠空列,于是元素拉伸占满整行。当元素多到能填满一行时,两者看起来一样。

怎么让网格元素跨两列?

在元素上使用 grid-column: span 2,或者给出起止网格线:grid-column: 1 / 3 覆盖第 1 列和第 2 列。grid-column: 1 / -1 跨满整个宽度,因为 -1 是最后一条网格线。

什么时候该用 grid 而不是 flexbox?

当行和列需要彼此对齐时用 grid:卡片画廊、标签对齐的表单、带页头、侧边栏和页脚的页面。当只有一行或一列、元素根据内容决定自身大小时用 flexbox,比如导航栏。

grid-gap 和 gap 一样吗?

一样。grid-gap、grid-row-gap 和 grid-column-gap 是旧名称。浏览器仍然把它们当作别名接受,但请写 gap、row-gap 和 column-gap,它们在 flexbox 中也能用。

Coddy 编程语言插图

和 Coddy 一起学编程

开始