初步认识
历史背景:从"一维流"到"二维网格"
Flex 的出现(2009 年左右,W3C 最终定稿于 2017)彻底改变了布局方式,它解决了"盒子之间的空间分配"这一长期痛点------能自动伸缩、自动对齐、自动换行。
但它有一个根本性局限:Flex 是"一维布局系统" ------ 只能在一条主轴(main-axis)上分配空间。
举例:
plain
display: flex;
无论你设置 flex-direction: row
还是 column
,Flex 只能沿这个方向伸缩。
多行(wrap)虽然能换行,但第二行的尺寸不会自动对齐第一行的列宽。
随着页面复杂度提高,人们遭遇了越来越多 Flex 难以解决的问题:
时代问题 | Flex 无法很好解决的场景 |
---|---|
仪表盘(Dashboard) | 需要行列双维度伸缩的面板 |
照片墙(Photo Wall) | 要求网格状对齐的多列布局 |
表格型界面 | 单元格之间要精准对齐、跨行跨列 |
复杂响应式页面 | 各区域要"行列"联动,不能仅靠一条轴 |
Flex 是"一维思维":
Flex 容器只关注主轴(main-axis)方向的空间分配;
交叉轴(cross-axis)只是用来对齐,而不参与主导布局。
于是,人们开始追求:
"能否让 CSS 像 Excel 一样思考布局?"
这就是 Grid Layout(网格布局) 出现的原因。
核心思想:把页面当作"二维坐标系"
Grid 的思想是:
不是先放内容再排列,而是 先画出网格,再放内容进去。
这就像设计报纸、仪表盘、后台面板:先画出"格子线",再把每个模块放入格子中。
plain
.container {
display: grid;
grid-template-columns: 200px 1fr 1fr;
grid-template-rows: 100px auto 50px;
}
这里我们实际上创建了一个"二维坐标平面":
plain
3列:200px | 1fr | 1fr
3行:100px | auto | 50px
每个子元素通过坐标放入:
plain
.item1 { grid-column: 1 / 3; grid-row: 1; }
.item2 { grid-column: 3; grid-row: 1 / 3; }
这意味着:item1 跨两列,item2 跨两行。
Grid 的定位:"线"是比"格子"更基础的东西
在传统表格(HTML <table>
)或 Flex 布局中,布局是围绕"单元格"展开的:
→ "我放到第1格、第2格"。
而 Grid 的革命性设计 就在于:
它不再直接定位格子,而是定位"线(grid line)"------格子之间的边界。
举例说明
假设你定义了:
plain
.container {
display: grid;
grid-template-columns: 100px 100px 100px;
}
这意味着容器内部的"网格线"(Grid Lines)如下图所示:
plain
| line 1 | cell 1 | line 2 | cell 2 | line 3 | cell 3 | line 4 |
有 3 个单元格(cell),但却有 4 条线(line)。
线的编号总是"格子数 + 1"。
grid-column
的语义
grid-column
是一个 简写属性,等价于:
plain
grid-column: <start-line> / <end-line>;
其中 <start-line>
表示"从第几条网格线(grid line)开始",
<end-line>
表示"到第几条网格线结束(不含该线)"。
简写形式
当你写:
plain
grid-column: 1;
它其实等价于:
plain
grid-column: 1 / auto;
👉 意思是:"从第1条网格线开始,结束线由浏览器自动决定(通常是占一列)。"
这就类似:
grid-column: 1 / 2
(仅占一列);- 如果结合
grid-column-end: span 2
,则表示跨两列。
案例:grid-column: 2 / 4
的含义
"从第2条网格线开始,到第4条网格线结束。"
换句话说,它跨越了:
- 第2条线 → 第3条线 → 第4条线之间的格子。
也就是第2格 + 第3格,共两个格子。
数学上可理解为 区间 [2,4),起点含,终点不含。
为什么要这么设计?(W3C 的深层逻辑)
Grid 的目标是 在二维空间中,精确描述"边界对齐"的盒子系统。
如果基于格子编号,会带来许多歧义:
- "第3列"到底是指起始线3到4,还是格子编号3?
- "跨2列"是指含第3列吗?
所以 W3C 干脆基于"线",直接描述"边界":
- 每个网格项由「起始线」和「结束线」界定;
- 更接近真实布局引擎的矩形逻辑(坐标是边界而不是中心)。
这就像绘图中:
plain
矩形 = 左边界 + 右边界 + 顶边 + 底边
Grid 的逻辑就是:
plain
grid-column-start / grid-column-end
grid-row-start / grid-row-end
/
语法只是简写形式
等价写法如下:
plain
.item1 {
grid-column-start: 2;
grid-column-end: 4;
grid-row-start: 1;
grid-row-end: 2;
}
等价于:
plain
.item1 {
grid-column: 2 / 4;
grid-row: 1 / 2;
}
还有一种"跨越"写法(更直观):
plain
.item1 {
grid-column: 2 / span 2; /* 从第2条线开始,跨2列 */
grid-row: 1 / span 1; /* 跨1行 */
}
这与 2 / 4
完全等价。
因为第2条线 + 跨2列 → 终止线为 2+2 = 4。
常见的几种写法
写法 | 意义 |
---|---|
grid-column: 1 / 3 |
从第1线到第3线,占第1、2列 |
grid-column: 2 / span 2 |
从第2列起,占两列宽度 |
grid-column: span 3 |
从当前位置起,占三列 |
grid-column: 1 |
自动结束,占一列 |
grid-column-start: 1; grid-column-end: -1; |
从第1线到最后一线,横跨全部列 |
Grid 的内部机制:一步步分配空间
阶段 1:建立轨道(Tracks)
Grid 把行和列称为 "轨道(track)",由三部分定义:
plain
grid-template-columns: 200px 1fr 1fr;
grid-template-rows: 100px auto 50px;
其中:
- 固定尺寸(px, em)是绝对长度;
fr
是"剩余空间分数"(fraction);auto
是内容自适应大小。
此阶段:
浏览器先统计固定尺寸 → 剩余空间再按 fr 比例分配。
阶段 2:放置网格项(Placement)
浏览器此时在解决什么问题?
"我有一堆格子(行列轨道),也有一堆元素(items)。
谁该放在哪个格子里?"
Grid 的放置分为两大类:
显式放置(Explicit Placement)
即开发者手动指定行列坐标:
plain
.item1 {
grid-column: 2 / 4; /* 从第2列到第4列 */
grid-row: 1 / 2; /* 从第1行到第2行 */
}
→ 浏览器直接放入指定单元格区域。
✅ 这类元素优先放置,不会参与自动算法。
自动放置(Auto Placement)
当某些 grid item 没指定行/列位置时,浏览器会这样处理:
Step 1:建立网格轨道
浏览器根据 grid-template-rows
和 grid-template-columns
创建初始网格。
Step 2:从左到右、从上到下扫描
浏览器会维护一个"自动放置光标(auto-placement cursor)",
依次在网格中查找空位,把未指定位置的 item 依次放进去。
比如:
plain
grid-template-columns: repeat(3, 100px);
grid-template-rows: repeat(2, 100px);
有 6 个格子。
假如你写:
plain
<div class="container">
<div class="item1" style="grid-column: 1 / 3;"></div>
<div class="item2"></div>
<div class="item3"></div>
</div>
放置过程为:
- item1 占用第1行第1-2列;
- 光标跳过被占的格子,从第1行第3列放 item2;
- 然后换行,在第2行第1列放 item3。
自动放置行为的控制属性: **grid-auto-flow**
它决定自动放置的方向与密度。
plain
grid-auto-flow: row | column | row dense | column dense;
值 | 含义 |
---|---|
row (默认) |
按行填充:从左到右,满一行换行。 |
column |
按列填充:从上到下,满一列换列。 |
row dense |
按行填充,同时尝试"回填"空隙(密集填充模式)。 |
column dense |
按列填充 + 回填。 |
密集填充模式(Dense Packing)
普通模式下,浏览器"尊重顺序":
- 即使前面 item 比较大,后面的 item 也不会插到它前面的小空隙中去。
而 dense 模式 会尽可能填满空隙,不考虑顺序。
例子👇
plain
.container {
display: grid;
grid-template-columns: repeat(3, 100px);
grid-auto-rows: 100px;
grid-auto-flow: row dense;
}
.item1 { grid-column: span 2; }
.item2 { grid-column: span 3; }
.item3 { grid-column: span 1; }
布局效果:
- 普通模式:item3 会在 item2 之后;
- dense 模式:item3 被"回放"进 item1 留下的小空隙中。
阶段 3:计算隐式轨道(Implicit Tracks)
浏览器此时在解决什么问题?
"元素太多,超出了我定义的格子怎么办?"
比如:
plain
.container {
display: grid;
grid-template-columns: 100px 100px 100px; /* 只有3列 */
}
.container div:nth-child(5) {
grid-column: 4; /* 第4列并不存在 */
}
这时浏览器会说:
"既然你要第4列,那我得'临时造'一条轨道。"
于是就诞生了"隐式轨道(Implicit Track)"。
显式 vs 隐式轨道
概念 | 产生方式 | 定义来源 |
---|---|---|
显式轨道(Explicit) | 由开发者定义 | grid-template-rows/columns |
隐式轨道(Implicit) | 由浏览器临时创建 | 超出定义的范围 |
隐式轨道的大小由什么决定?
由以下两个属性控制:
plain
grid-auto-rows: <length> | <percentage> | auto;
grid-auto-columns: <length> | <percentage> | auto;
例子:
plain
.container {
display: grid;
grid-template-columns: 100px 100px;
grid-auto-columns: 200px;
}
.item3 {
grid-column: 3; /* 会触发创建一个隐式第3列 */
}
此时浏览器:
- 检测到你要第3列;
- 发现你定义了
grid-auto-columns: 200px
; - 创建一个新列,宽度 200px。
如果没写 grid-auto-columns
,默认是 auto
(即根据内容大小决定)。
隐式轨道何时常见?
- 自动放置时,元素太多导致溢出;
- 明确指定坐标超出定义;
- 启用
grid-auto-flow: column
时元素过多。
隐式轨道与"隐式网格"密切相关:Grid 实际上维护了一个 隐式网格表,所有显式 + 隐式轨道共同组成"渲染用的最终网格"。
阶段 4:空间分配算法(Track Sizing Algorithm)
这是整个 Grid 最复杂的阶段,Flex 的伸缩算法只能算它的"一个特例"。
让我们从"浏览器的角度"一步步看它的思考逻辑。
浏览器此时在解决什么问题?
"我已经知道了有多少行、多少列,也知道每条轨道的类型(px、auto、fr)。
那我该如何分配最终的宽高呢?"
这时,浏览器开始执行 W3C 规范中的 轨道尺寸分配算法(Track Sizing Algorithm)。
固定轨道先分配
优先处理固定尺寸轨道,锁死大小。
plain
grid-template-columns: 100px 1fr 2fr;
此时:
- 第一列固定为 100px;
- 剩余空间准备分配给第二、三列。
自动轨道(auto)根据内容计算
auto
轨道根据"最小内容尺寸(min-content)"或"最大内容尺寸(max-content)"决定初始大小。
通俗解释:
min-content
:让内容不换行的最小宽度;max-content
:让内容完全展开所需宽度。
浏览器先测量内容大小,再决定轨道宽度。
若设置 minmax(100px, auto)
,会保证至少 100px。
剩余空间分配给 fr
轨道
类似于 Flex 的 flex-grow
,fr
是剩余空间的分数。
计算步骤:
- 统计所有固定 + auto 轨道的宽度;
- 剩余空间 = 容器宽度 - 已分配宽度;
- 再按
fr
权重分配:
例如:
plain
grid-template-columns: 200px 1fr 2fr;
- 固定列:200px;
- 剩余空间 = 总宽度 - 200px;
- 第二列 = 剩余空间 × 1/3;
- 第三列 = 剩余空间 × 2/3。
行高(cross-axis)同理计算
行的算法几乎一样,只是方向换成垂直。
grid-template-rows
也能使用 px、auto、fr、minmax()。
最终阶段:对齐与收尾
在计算完所有轨道大小后,浏览器再执行:
- 对齐(align-content / justify-content)
- gap(行列间距)
- 溢出修正(overflow)
最终生成一个精确的二维坐标网格,每个格子都能映射到具体像素位置。