Grid 网格布局:CSS 布局的二维革命
想象你在设计一个报纸的版面。报纸有清晰的行和列,标题可能横跨几列,图片可能占据多行,文章分栏排列。你需要同时在水平和垂直方向上精确控制每个元素的位置和大小。这就是二维布局的典型场景。
在 Grid 布局出现之前,创建这样的复杂布局需要大量的嵌套 div、复杂的计算和各种布局技巧的组合。而 Grid 布局就像是为网页设计师准备的一张专业的设计网格纸,让你可以自由地在行和列的交叉点上放置元素,轻松实现复杂的二维布局。
为什么需要 Grid
传统布局的局限
我们已经有了 Flexbox,为什么还需要 Grid?因为它们解决的是不同的问题。
Flexbox 是一维布局模型,它一次只能处理一个方向——要么是行,要么是列。当你尝试用 Flexbox 创建复杂的二维布局时,你会发现自己陷入了多层嵌套的困境。
让我们看一个实际例子。假设你要创建一个典型的网站布局:
+------------------+------------------+
| Header |
+------------------+------------------+
| Sidebar | Main Content |
| | |
| +------------------------+
| | Aside |
+------------------+------------------+
| Footer |
+------------------+------------------+使用 Flexbox,你需要这样做:
<div class="page">
<header>Header</header>
<div class="content-wrapper">
<!-- 需要额外的包裹元素 -->
<aside class="sidebar">Sidebar</aside>
<div class="main-and-aside">
<!-- 又一个包裹元素 -->
<main>Main Content</main>
<aside class="aside">Aside</aside>
</div>
</div>
<footer>Footer</footer>
</div>.page {
display: flex;
flex-direction: column;
}
.content-wrapper {
display: flex;
flex: 1;
}
.sidebar {
flex: 0 0 200px;
}
.main-and-aside {
display: flex; /* 第三层 flex 嵌套 */
flex-direction: column;
flex: 1;
}
.main-and-aside main {
flex: 1;
}看到了吗?你需要添加额外的包裹元素,使用多层嵌套的 Flexbox,而且 HTML 结构变得不够语义化。
Grid 的优雅解决方案
现在让我们用 Grid 来实现同样的布局:
<div class="page">
<header>Header</header>
<aside class="sidebar">Sidebar</aside>
<main>Main Content</main>
<aside class="aside">Aside</aside>
<footer>Footer</footer>
</div>.page {
display: grid;
grid-template-columns: 200px 1fr; /* 两列:固定宽度的侧边栏、弹性的内容区 */
grid-template-rows: auto 1fr 1fr auto; /* 四行:header、main、aside、footer */
min-height: 100vh;
}
header {
grid-column: 1 / 3; /* 横跨两列 */
grid-row: 1;
}
.sidebar {
grid-column: 1;
grid-row: 2 / 4; /* 从第2行跨越到第4行(占据main和aside的高度) */
}
main {
grid-column: 2;
grid-row: 2; /* 第二行 */
}
.aside {
grid-column: 2;
grid-row: 3; /* 第三行 */
}
footer {
grid-column: 1 / 3; /* 横跨两列 */
grid-row: 4;
}看到区别了吗?HTML 结构更简洁、更语义化,不需要额外的包裹元素。CSS 代码虽然看起来有点陌生,但逻辑清晰:我们定义了一个 2×4 的网格(2 列 4 行),然后告诉每个元素它应该占据哪些单元格。
Grid 的核心概念
要掌握 Grid 布局,你需要理解几个核心概念。这些概念就像是 Grid 的"词汇表",一旦理解了,你就能流畅地使用 Grid 进行布局。
网格容器和网格项目
与 Flexbox 类似,Grid 布局也基于容器和项目的关系:
<div class="grid-container">
<!-- 这是 grid 容器 -->
<div class="grid-item">Item 1</div>
<!-- 这些是 grid 项目 -->
<div class="grid-item">Item 2</div>
<div class="grid-item">Item 3</div>
</div>.grid-container {
display: grid; /* 创建网格容器 */
}一旦你给元素设置了 display: grid,它就变成了网格容器,它的直接子元素自动成为网格项目。
网格线(Grid Lines)
网格线是构成网格结构的基础。想象你在纸上画了一个 3×3 的方格,你需要 4 条垂直线和 4 条水平线。
1 2 3 4 ← 垂直网格线的编号
┌───┬───┬───┐ 1 ← 水平网格线的编号
│ │ │ │
├───┼───┼───┤ 2
│ │ │ │
├───┼───┼───┤ 3
│ │ │ │
└───┴───┴───┘ 4在 CSS Grid 中,网格线从 1 开始编号。一个 3 列的网格有 4 条垂直线(1、2、3、4),一个 3 行的网格有 4 条水平线(1、2、3、4)。
你可以用这些编号来精确定位元素:
.item {
grid-column-start: 1; /* 从第 1 条垂直线开始 */
grid-column-end: 3; /* 到第 3 条垂直线结束 */
/* 相当于横跨第 1 列和第 2 列 */
}更简洁的写法是使用简写属性:
.item {
grid-column: 1 / 3; /* 从 1 到 3 */
}网格轨道(Grid Tracks)
网格轨道是两条相邻网格线之间的空间,可以是行轨道(水平)或列轨道(垂直)。
列轨道1 列轨道2 列轨道3
┌─────┬─────┬─────┐
│ │ │ │ 行轨道1
├─────┼─────┼─────┤
│ │ │ │ 行轨道2
├─────┼─────┼─────┤
│ │ │ │ 行轨道3
└─────┴─────┴─────┘当你定义 grid-template-columns: 200px 300px 400px 时,你创建了三个列轨道,宽度分别是 200px、300px 和 400px。
网格单元(Grid Cells)
网格单元是四条网格线围成的最小空间,就像表格中的一个单元格。
┌─────┬─────┐
│单元格│单元格│
├─────┼─────┤
│单元格│单元格│
└─────┴─────┘一个网格项目可以占据一个或多个网格单元。
网格区域(Grid Areas)
网格区域是由任意数量的网格单元组成的矩形区域。一个网格项目可以占据一个网格区域。
┌─────┬─────┬─────┐
│ A │ B │
│ ├─────┤
│ │ C │
└─────┴─────┴─────┘在这个例子中,A 占据 2×2 的区域,B 和 C 各占据 1×1 的区域。
创建你的第一个 Grid 布局
让我们从一个简单的例子开始,逐步理解 Grid 布局的工作原理。
基础网格
首先创建一个简单的 3×3 网格:
<div class="grid">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
<div class="item">7</div>
<div class="item">8</div>
<div class="item">9</div>
</div>.grid {
display: grid;
grid-template-columns: 100px 100px 100px; /* 三列,每列 100px */
grid-template-rows: 100px 100px 100px; /* 三行,每行 100px */
gap: 10px; /* 网格项目之间的间距 */
background-color: #f5f5f5;
padding: 10px;
}
.item {
background-color: #2196f3;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: bold;
border-radius: 4px;
}这段代码创建了一个 3×3 的网格,每个单元格都是 100px × 100px。9 个项目会自动按照从左到右、从上到下的顺序填充这些单元格。
使用 fr 单位
fr(fraction 的缩写)是 Grid 布局引入的一个新单位,表示可用空间的一份。它让网格变得真正弹性。
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr; /* 三列平分空间 */
gap: 10px;
}这段代码创建了三个等宽的列,它们会平分容器的宽度。如果容器宽度是 900px,扣除两个间距(2 × 10px),剩余 880px 会被平分成三份,每个 fr 约等于 293.3px。
你可以使用不同的 fr 值来创建不同比例的列:
.grid {
display: grid;
grid-template-columns: 2fr 1fr 1fr; /* 第一列是其他列的两倍宽 */
gap: 10px;
}如果容器宽度是 900px,扣除间距后剩余 880px。总共有 4 份(2 + 1 + 1),每份是 220px。所以第一列是 440px,第二列和第三列各是 220px。
混合使用不同单位
Grid 的强大之处在于你可以混合使用不同的单位:
.grid {
display: grid;
grid-template-columns: 200px 1fr 2fr; /* 固定 + 弹性 + 弹性 */
gap: 10px;
}在这个例子中,第一列始终是 200px。剩余的空间会被分成 3 份(1 + 2),第二列占 1 份,第三列占 2 份。
假设容器宽度是 900px:
- 第一列:200px(固定)
- 剩余空间:900px - 200px - 20px(间距)= 680px
- 第二列:680px × (1/3) ≈ 226.7px
- 第三列:680px × (2/3) ≈ 453.3px
repeat() 函数
当你有很多相同的轨道时,repeat() 函数可以让代码更简洁:
/* 传统写法 */
.grid {
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
}
/* 使用 repeat() */
.grid {
grid-template-columns: repeat(5, 1fr); /* 重复 5 次 1fr */
}repeat() 还可以重复一个模式:
.grid {
grid-template-columns: repeat(3, 100px 200px);
/* 相当于: 100px 200px 100px 200px 100px 200px */
}这会创建 6 列,按照 100px、200px 的模式重复 3 次。
实际应用场景
让我们看几个实际的应用场景,感受 Grid 布局的实际威力。
图片画廊
Grid 非常适合创建图片画廊:
<div class="gallery">
<div class="photo photo-1">Photo 1</div>
<div class="photo photo-2">Photo 2</div>
<div class="photo photo-3">Photo 3</div>
<div class="photo photo-4">Photo 4</div>
<div class="photo photo-5">Photo 5</div>
<div class="photo photo-6">Photo 6</div>
</div>.gallery {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 3 列 */
grid-auto-rows: 200px; /* 每行 200px */
gap: 15px;
padding: 20px;
}
.photo {
background-color: #ddd;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
overflow: hidden;
}
/* 让第一张照片占据 2×2 的空间 */
.photo-1 {
grid-column: 1 / 3; /* 横跨两列 */
grid-row: 1 / 3; /* 横跨两行 */
background-color: #2196f3;
color: white;
font-size: 24px;
}在这个例子中,第一张照片占据了 2×2 的空间,成为焦点,其他照片各占据 1×1 的空间。Grid 会自动调整其他照片的位置,不需要你手动计算。
卡片布局
Grid 也非常适合创建响应式的卡片布局:
<div class="cards">
<div class="card">
<h3>Basic Plan</h3>
<p class="price">$9/month</p>
<ul>
<li>10 Projects</li>
<li>5GB Storage</li>
<li>Email Support</li>
</ul>
<button>Choose Plan</button>
</div>
<div class="card">
<h3>Pro Plan</h3>
<p class="price">$29/month</p>
<ul>
<li>Unlimited Projects</li>
<li>50GB Storage</li>
<li>Priority Support</li>
<li>Advanced Analytics</li>
</ul>
<button>Choose Plan</button>
</div>
<div class="card">
<h3>Enterprise</h3>
<p class="price">$99/month</p>
<ul>
<li>Unlimited Everything</li>
<li>500GB Storage</li>
<li>24/7 Phone Support</li>
<li>Custom Integration</li>
</ul>
<button>Choose Plan</button>
</div>
</div>.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
/* auto-fit: 自动填充
minmax(250px, 1fr): 最小 250px,最大 1fr */
gap: 20px;
padding: 20px;
}
.card {
background-color: white;
border: 1px solid #ddd;
border-radius: 8px;
padding: 30px;
display: flex;
flex-direction: column;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.card h3 {
margin: 0 0 10px 0;
color: #333;
font-size: 24px;
}
.card .price {
font-size: 32px;
font-weight: bold;
color: #2196f3;
margin: 10px 0;
}
.card ul {
flex-grow: 1;
padding-left: 20px;
margin: 20px 0;
}
.card li {
margin: 10px 0;
}
.card button {
padding: 12px 24px;
background-color: #2196f3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}这个布局的神奇之处在于 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)):
auto-fit:根据容器宽度自动计算能放下多少列minmax(250px, 1fr):每列最小 250px,最大 1fr
这意味着:
- 在宽屏幕上,卡片会排成多列
- 当屏幕变窄,放不下三列时,会自动变成两列
- 再窄一些,会变成一列
- 完全不需要媒体查询!
杂志式布局
Grid 非常适合创建类似杂志的复杂布局:
<div class="magazine">
<header class="header">Magazine Header</header>
<article class="featured">Featured Article</article>
<article class="article-1">Article 1</article>
<article class="article-2">Article 2</article>
<article class="article-3">Article 3</article>
<aside class="sidebar">Sidebar</aside>
<footer class="footer">Footer</footer>
</div>.magazine {
display: grid;
grid-template-columns: repeat(12, 1fr); /* 12 列网格系统 */
grid-auto-rows: minmax(100px, auto);
gap: 20px;
padding: 20px;
}
.header {
grid-column: 1 / 13; /* 横跨所有 12 列 */
background-color: #333;
color: white;
padding: 30px;
text-align: center;
font-size: 32px;
}
.featured {
grid-column: 1 / 9; /* 占据左边 8 列 */
grid-row: 2 / 4; /* 占据 2 行 */
background-color: #2196f3;
color: white;
padding: 30px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
}
.article-1 {
grid-column: 9 / 13; /* 占据右边 4 列 */
background-color: #e3f2fd;
padding: 20px;
}
.article-2 {
grid-column: 9 / 13;
background-color: #e3f2fd;
padding: 20px;
}
.article-3 {
grid-column: 1 / 7; /* 占据左边 6 列 */
background-color: #f5f5f5;
padding: 20px;
}
.sidebar {
grid-column: 7 / 13; /* 占据右边 6 列 */
background-color: #fff3e0;
padding: 20px;
}
.footer {
grid-column: 1 / 13;
background-color: #333;
color: white;
padding: 20px;
text-align: center;
}这个布局使用了 12 列网格系统(就像 Bootstrap 一样),但比 Bootstrap 更灵活。每个元素可以精确地指定它占据哪些列,创建出复杂而优雅的布局。
Grid vs Flexbox:何时使用哪个?
很多开发者会困惑:应该用 Grid 还是 Flexbox?答案是两者都用,因为它们解决不同的问题。
使用 Flexbox 的场景
- 一维布局:导航栏、工具栏等只需要在一个方向上排列的元素
- 内容驱动:当你希望元素根据内容大小自然排列时
- 小规模布局:组件内部的小型布局
- 对齐控制:当主要目的是对齐和分配空间时
/* Flexbox 适合的场景 */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
}使用 Grid 的场景
- 二维布局:需要同时控制行和列的布局
- 布局驱动:当你有明确的布局结构,想要元素填充这个结构时
- 整体页面结构:复杂的页面布局
- 精确控制:需要精确控制元素位置和大小时
/* Grid 适合的场景 */
.page-layout {
display: grid;
grid-template-columns: 200px 1fr 300px;
grid-template-rows: auto 1fr auto;
}组合使用
在实际项目中,最佳实践是组合使用两者:
/* 页面整体使用 Grid */
.page {
display: grid;
grid-template-columns: 250px 1fr;
grid-template-rows: auto 1fr auto;
}
/* 导航栏使用 Flexbox */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
}
/* 卡片容器使用 Grid */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
/* 每个卡片内部使用 Flexbox */
.card {
display: flex;
flex-direction: column;
}浏览器支持
Grid 布局的浏览器支持非常好。现代浏览器都完全支持:
- Chrome 57+ (2017 年 3 月)
- Firefox 52+ (2017 年 3 月)
- Safari 10.1+ (2017 年 3 月)
- Edge 16+ (2017 年 10 月)
- iOS Safari 10.3+ (2017 年 3 月)
- Android Browser 67+ (2018 年 6 月)
有趣的是,所有现代浏览器几乎在同一时间(2017 年 3 月)开始支持 Grid 布局。这是因为浏览器厂商达成了共识,同时发布了对 Grid 的支持。
对于需要支持 IE 11 的项目,IE 11 支持旧版本的 Grid 语法(使用 -ms- 前缀),但功能有限。对于大多数现代项目,你可以放心使用 Grid,不需要任何 polyfill。
总结
Grid 布局是 CSS 布局的一次革命性进步,它为二维布局提供了原生的、强大的解决方案。
Grid 的核心优势:
- 二维布局:同时控制行和列,创建复杂的布局结构
- 直观清晰:布局意图一目了然,代码易于理解和维护
- 精确控制:可以精确指定每个元素的位置和大小
- 灵活响应:配合 fr 单位和 auto-fit,轻松实现响应式布局
- 减少嵌套:不需要额外的包裹元素,HTML 结构更简洁
核心概念回顾:
- 网格容器和项目:
display: grid创建容器,直接子元素成为项目 - 网格线:构成网格的基础,从 1 开始编号
- 网格轨道:两条相邻网格线之间的空间
- 网格单元:最小的网格空间
- 网格区域:由多个网格单元组成的矩形区域
- fr 单位:Grid 特有的弹性单位,表示可用空间的份数
使用建议:
- 对于简单的一维布局,使用 Flexbox
- 对于复杂的二维布局,使用 Grid
- 在实际项目中,组合使用两者发挥各自优势
- 用 Grid 构建整体布局,用 Flexbox 处理组件内部
在接下来的章节中,我们将深入学习 Grid 容器的各种属性、Grid 项目的各种属性、以及如何使用模板区域创建语义化的布局。掌握 Grid,你将拥有创建任何复杂布局的能力。