前言
CSS 布局经历了从表格布局(<table>)到浮动布局(float),再到定位布局(position)的演进。每种方法都有各自的局限。直到 Flexbox(弹性盒子)和 Grid(网格布局)的出现,CSS 才真正拥有了现代、强大且直观的布局系统。
本文将用 8000+ 字的篇幅,从原理到实战,深入剖析 Flexbox 和 Grid 的每一项核心属性、计算规则以及经典的布局方案。每个知识点都配有完整可运行的 HTML/CSS 代码,你可以直接复制到浏览器中验证。
第一章:Flexbox 详解
Flexbox(Flexible Box Layout Module)是 CSS3 引入的一维布局模型。所谓"一维",是指它一次只处理一个方向的排列——要么是水平方向的行(row),要么是垂直方向的列(column)。
1.1 基础概念
Flexbox 由两个核心要素构成:
- Flex 容器(flex container):设置了
display: flex或display: inline-flex的父元素。 - Flex 项目(flex item):容器内的直接子元素。
有两个轴贯穿始终:
- 主轴(main axis):由
flex-direction决定的方向。默认为row,主轴为水平方向,从左到右。 - 交叉轴(cross axis):与主轴垂直的方向。
主轴 (main axis) ──────────────────────>
┌──────┬──────┬──────┬──────┬──────┬──────┐
│ item │ item │ item │ item │ item │ item │ 交叉轴
├──────┼──────┼──────┼──────┼──────┼──────┤ (cross
│ item │ item │ item │ item │ item │ item │ axis)
└──────┴──────┴──────┴──────┴──────┴──────┘ │
▼1.2 容器属性
1.2.1 flex-direction
控制主轴方向,即项目的排列方向。
| 值 | 方向 | 起点 |
|---|---|---|
row(默认) |
水平,从左到右 | 左边缘 |
row-reverse |
水平,从右到左 | 右边缘 |
column |
垂直,从上到下 | 上边缘 |
column-reverse |
垂直,从下到上 | 下边缘 |
<div class="demo-box">
<style>
.flex-container {
display: flex;
flex-direction: row; /* row | row-reverse | column | column-reverse */
padding: 12px;
background: #f0f4f8;
border-radius: 8px;
}
.flex-container .item {
background: #3b82f6;
color: white;
padding: 16px 24px;
margin: 4px;
border-radius: 4px;
font-size: 14px;
}
</style>
<div class="flex-container" style="flex-direction: row;">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
</div>
<p style="margin-top:8px;color:#666;font-size:13px;">↑ flex-direction: row(默认水平排列)</p>
</div>1.2.2 flex-wrap
控制项目是否换行。默认情况下,所有项目排在一行。
| 值 | 行为 |
|---|---|
nowrap(默认) |
不换行,项目可能被压缩 |
wrap |
换行,从交叉轴起点开始 |
wrap-reverse |
换行,从交叉轴终点开始 |
<div class="demo-box">
<style>
.flex-wrap-demo {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 12px;
background: #f0f4f8;
border-radius: 8px;
width: 280px;
}
.flex-wrap-demo .item {
background: #8b5cf6;
color: white;
padding: 12px 16px;
border-radius: 4px;
flex: 0 0 80px;
text-align: center;
font-size: 13px;
}
</style>
<div class="flex-wrap-demo">
<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>
<p style="margin-top:8px;color:#666;font-size:13px;">↑ flex-wrap: wrap,超出宽度自动换行</p>
</div>1.2.3 justify-content
定义项目在主轴上的对齐方式。这是最常用的 Flexbox 属性之一。
| 值 | 效果 |
|---|---|
flex-start(默认) |
从主轴的起始端排列 |
flex-end |
从主轴的末端排列 |
center |
居中排列 |
space-between |
两端对齐,项目之间的间隔相等 |
space-around |
每个项目两侧的间隔相等 |
space-evenly |
项目之间的间隔和项目与容器边缘的间隔都相等 |
<div class="demo-box">
<style>
.justify-demo {
display: flex;
padding: 12px;
background: #f0f4f8;
border-radius: 8px;
margin-bottom: 12px;
}
.justify-demo .item {
background: #10b981;
color: white;
padding: 12px 20px;
border-radius: 4px;
font-size: 13px;
}
.justify-label {
font-size: 12px;
color: #888;
margin-top: 4px;
margin-bottom: 16px;
}
</style>
<div class="justify-demo" style="justify-content: flex-start;">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
</div>
<div class="justify-label">↑ justify-content: flex-start</div>
<div class="justify-demo" style="justify-content: center;">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
</div>
<div class="justify-label">↑ justify-content: center</div>
<div class="justify-demo" style="justify-content: space-between;">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
</div>
<div class="justify-label">↑ justify-content: space-between</div>
<div class="justify-demo" style="justify-content: space-evenly;">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
</div>
<div class="justify-label">↑ justify-content: space-evenly</div>
</div>1.2.4 align-items
定义项目在交叉轴上的对齐方式。
| 值 | 效果 |
|---|---|
stretch(默认) |
如果项目未设置高度或宽度,拉伸占满整个容器 |
flex-start |
从交叉轴起始端对齐 |
flex-end |
从交叉轴末端对齐 |
center |
交叉轴居中 |
baseline |
项目的第一行文字基线对齐 |
<div class="demo-box">
<style>
.align-items-demo {
display: flex;
align-items: center;
height: 120px;
padding: 12px;
background: #f0f4f8;
border-radius: 8px;
border: 1px dashed #ccc;
}
.align-items-demo .item {
background: #f59e0b;
color: white;
padding: 8px 20px;
border-radius: 4px;
font-size: 13px;
}
.align-items-demo .item.tall { padding: 24px 20px; }
.align-items-demo .item.short { padding: 4px 20px; }
</style>
<div class="align-items-demo" style="align-items: center;">
<div class="item tall">高</div>
<div class="item">正常</div>
<div class="item short">矮</div>
<div class="item tall">高</div>
</div>
<p style="margin-top:8px;color:#666;font-size:13px;">↑ align-items: center — 所有项目在交叉轴(垂直方向)居中</p>
</div>1.2.5 align-content
当 flex-wrap 产生多行时,align-content 控制多行在交叉轴上的对齐方式。单行时该属性无效。
| 值 | 效果 |
|---|---|
stretch(默认) |
行占满整个交叉轴 |
flex-start |
从交叉轴起始端堆叠 |
flex-end |
从交叉轴末端堆叠 |
center |
交叉轴居中 |
space-between |
行之间等间距 |
space-around |
每行两侧间距相等 |
<div class="demo-box">
<style>
.align-content-demo {
display: flex;
flex-wrap: wrap;
align-content: center;
height: 200px;
padding: 12px;
background: #f0f4f8;
border-radius: 8px;
border: 1px dashed #ccc;
}
.align-content-demo .item {
background: #ec4899;
color: white;
padding: 12px 24px;
margin: 4px;
border-radius: 4px;
font-size: 13px;
}
</style>
<div class="align-content-demo" style="align-content: center;">
<div class="item">行 1 - 1</div>
<div class="item">行 1 - 2</div>
<div class="item">行 1 - 3</div>
<div class="item">行 2 - 1</div>
<div class="item">行 2 - 2</div>
<div class="item">行 2 - 3</div>
<div class="item">行 3 - 1</div>
</div>
<p style="margin-top:8px;color:#666;font-size:13px;">↑ align-content: center — 多行在容器内整体垂直居中</p>
</div>1.2.6 gap
gap 属性定义 flex 项目之间的间距,替代了早期的 margin hack。
.container {
display: flex;
flex-wrap: wrap;
gap: 16px; /* 行和列间距都是 16px */
/* gap: 16px 24px; */ /* 行间距 16px,列间距 24px */
}注意:
gap在 Flexbox 中的浏览器支持已经很完善(Chrome 84+、Firefox 63+、Safari 14.1+),可以放心使用。
1.3 项目属性
1.3.1 flex-grow
定义项目的放大比例。默认值为 0,即即使有剩余空间也不放大。
计算规则:
- 计算所有项目的
flex-grow之和 - 剩余空间 × (项目 flex-grow / flex-grow 总和)= 该项目的额外宽度
<div class="demo-box">
<style>
.grow-demo {
display: flex;
padding: 12px;
background: #f0f4f8;
border-radius: 8px;
}
.grow-demo .item {
color: white;
padding: 16px 24px;
border-radius: 4px;
font-size: 14px;
text-align: center;
}
</style>
<div class="grow-demo">
<div class="item" style="background:#3b82f6; flex-grow: 1;">flex-grow: 1</div>
<div class="item" style="background:#10b981; flex-grow: 2;">flex-grow: 2</div>
<div class="item" style="background:#f59e0b; flex-grow: 1;">flex-grow: 1</div>
</div>
<p style="margin-top:8px;color:#666;font-size:13px;">
↑ 剩余空间按 1:2:1 分配。flex-grow:2 的绿色项目获得的空间是其他项目的两倍。
</p>
</div>1.3.2 flex-shrink
定义项目的缩小比例。默认值为 1,即空间不足时项目按比例缩小。
收缩规则(理解这一点至关重要):
当所有项目的总宽度超过容器宽度时,每个项目缩小的量 = 超出宽度 × (项目宽度 × flex-shrink) / (所有项目宽度×flex-shrink 之和)。
<div class="demo-box">
<style>
.shrink-demo {
display: flex;
width: 400px;
padding: 12px;
background: #f0f4f8;
border-radius: 8px;
}
.shrink-demo .item {
width: 200px; /* 三项共 600px,容器 400px,超出 200px */
color: white;
padding: 16px;
border-radius: 4px;
font-size: 14px;
text-align: center;
box-sizing: border-box;
}
</style>
<div class="shrink-demo">
<div class="item" style="background:#3b82f6; flex-shrink: 1;">shrink:1</div>
<div class="item" style="background:#10b981; flex-shrink: 2;">shrink:2<br>缩小更多</div>
<div class="item" style="background:#f59e0b; flex-shrink: 0;">shrink:0<br>不缩小</div>
</div>
<p style="margin-top:8px;color:#666;font-size:13px;">
↑ 第三个项目的 flex-shrink:0,永远不会被压缩。超出的宽度由前两项按 1:2 比例分担。
</p>
</div>1.3.3 flex-basis
定义项目在分配剩余空间之前的初始大小。可以设为具体的长度值(px、em、%)、auto(默认,等同于项目的 width/height)或 content。
.item {
flex-basis: 200px; /* 初始宽度为 200px */
/* flex-basis: 30%; */ /* 初始宽度为容器宽度的 30% */
}当 flex-direction: column 时,flex-basis 控制的是高度。
1.3.4 flex(简写属性)
强烈推荐使用 flex 简写,而不是单独设置三个属性。
/* 语法:flex: flex-grow flex-shrink flex-basis */
.item {
flex: 1; /* 等价于 flex: 1 1 0% — 均匀分配空间 */
flex: auto; /* 等价于 flex: 1 1 auto — 按内容大小分配 */
flex: none; /* 等价于 flex: 0 0 auto — 固定大小,不伸缩 */
flex: 0 0 200px; /* 固定 200px,不伸缩 */
flex: 1 1 300px; /* 最小 300px,可伸缩 */
}常见简写模式:
/* ——— 等分布局(所有项目宽度相等) ——— */
.item { flex: 1; } /* 所有项目 flex-grow 都为 1,等分容器宽度 */
/* ——— 固定 + 弹性组合 ——— */
.sidebar { flex: 0 0 250px; } /* 固定 250px,不伸缩 */
.main { flex: 1 1 0%; } /* 占据剩余全部空间 */
/* ——— 自适应内容 ——— */
.item { flex: 0 1 auto; } /* 根据内容确定大小,必要时收缩 */1.3.5 align-self
允许单个项目覆盖容器的 align-items 设置。
.item:first-child {
align-self: flex-start; /* 只有这个项目在交叉轴顶端对齐 */
}
.item:last-child {
align-self: flex-end; /* 只有这个项目在交叉轴底端对齐 */
}1.3.6 order
定义项目的排列顺序。数值越小排列越靠前,默认值为 0。
.item:nth-child(1) { order: 3; }
.item:nth-child(2) { order: 1; } /* 最先显示 */
.item:nth-child(3) { order: 2; }注意:
order只改变视觉顺序,不影响 DOM 顺序和键盘导航顺序。对可访问性有影响,谨慎使用。
1.4 Flex 计算规则详解
Flexbox 的计算分三步:
第一步:确定初始大小(flex-basis)
- 如果
flex-basis为具体值,取该值。 - 如果
flex-basis: auto,取项目的width/height或内容大小。
第二步:计算剩余空间
剩余空间 = 容器大小 - 所有项目的 flex-basis 之和 - gap 总和第三步:分配或收缩
- 如果剩余空间 > 0:按
flex-grow比例分配。 - 如果剩余空间 < 0:按
flex-shrink比例收缩。
一个完整的计算示例:
容器宽度:1000px
项目 A: flex: 1 1 200px
项目 B: flex: 2 1 100px
项目 C: flex: 1 2 300px
第一步:初始大小 = 200 + 100 + 300 = 600px
第二步:剩余空间 = 1000 - 600 = 400px
第三步:flex-grow 总和 = 1 + 2 + 1 = 4
A 最终宽度 = 200 + 400 × (1/4) = 300px
B 最终宽度 = 100 + 400 × (2/4) = 300px
C 最终宽度 = 300 + 400 × (1/4) = 400px1.5 常见布局模式
1.5.1 水平居中
<div class="demo-box">
<style>
.center-h {
display: flex;
justify-content: center;
padding: 16px;
background: #f0f4f8;
border-radius: 8px;
}
.center-h .box {
background: #3b82f6;
color: white;
padding: 16px 32px;
border-radius: 4px;
}
</style>
<div class="center-h">
<div class="box">水平居中</div>
</div>
</div>1.5.2 垂直居中(经典 flex 居中方案)
<div class="demo-box">
<style>
.center-v {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
background: #f0f4f8;
border-radius: 8px;
}
.center-v .box {
background: #8b5cf6;
color: white;
padding: 24px 40px;
border-radius: 8px;
text-align: center;
}
</style>
<div class="center-v">
<div class="box">完美居中<br>水平 + 垂直</div>
</div>
</div>1.5.3 圣杯布局(Flexbox 版)
圣杯布局指:页头(header)、页脚(footer)固定高度,中间是 左侧边栏 + 主体 + 右侧边栏 的三栏结构。
<div class="demo-box">
<style>
.holy-grail {
display: flex;
flex-direction: column;
min-height: 400px;
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
}
.holy-grail .header,
.holy-grail .footer {
background: #1e293b;
color: white;
padding: 16px 24px;
text-align: center;
font-size: 14px;
}
.holy-grail .middle {
display: flex;
flex: 1;
}
.holy-grail .left {
flex: 0 0 180px;
background: #f59e0b;
color: white;
padding: 20px;
}
.holy-grail .center {
flex: 1;
background: #f8fafc;
padding: 20px;
}
.holy-grail .right {
flex: 0 0 180px;
background: #10b981;
color: white;
padding: 20px;
}
</style>
<div class="holy-grail">
<div class="header">📰 页头(Header)</div>
<div class="middle">
<div class="left">左侧边栏 180px</div>
<div class="center">
<strong>主体内容区(弹性)</strong>
<p style="margin-top:8px;color:#475569;">中间的 main 区域使用 flex:1,会自动占满剩余空间。</p>
</div>
<div class="right">右侧边栏 180px</div>
</div>
<div class="footer">© 页脚(Footer)</div>
</div>
</div>1.5.4 等分布局
<div class="demo-box">
<style>
.equal-grid {
display: flex;
gap: 12px;
padding: 12px;
background: #f0f4f8;
border-radius: 8px;
}
.equal-grid .item {
flex: 1;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
padding: 24px 16px;
border-radius: 6px;
text-align: center;
font-size: 14px;
}
</style>
<div class="equal-grid">
<div class="item">1/4</div>
<div class="item">1/4</div>
<div class="item">1/4</div>
<div class="item">1/4</div>
</div>
<p style="margin-top:8px;color:#666;font-size:13px;">↑ 四个项目各占 1/4,使用 flex:1 等分容器</p>
</div>1.5.5 固定 + 弹性组合
<div class="demo-box">
<style>
.fixed-flex {
display: flex;
gap: 12px;
padding: 12px;
background: #f0f4f8;
border-radius: 8px;
}
.fixed-flex .sidebar {
flex: 0 0 200px;
background: #ef4444;
color: white;
padding: 24px 16px;
border-radius: 6px;
text-align: center;
}
.fixed-flex .main {
flex: 1;
background: white;
padding: 24px 16px;
border-radius: 6px;
text-align: center;
border: 1px solid #e2e8f0;
}
</style>
<div class="fixed-flex">
<div class="sidebar">固定 200px</div>
<div class="main">弹性伸缩区(占满剩余空间)</div>
</div>
</div>1.6 响应式 Flexbox:flex-wrap + flex-basis
使用 flex-wrap 配合 flex-basis 可以轻松实现自适应网格。
<div class="demo-box">
<style>
.responsive-grid {
display: flex;
flex-wrap: wrap;
gap: 16px;
padding: 12px;
background: #f0f4f8;
border-radius: 8px;
}
.responsive-grid .card {
flex: 1 1 200px; /* 最小 200px,可伸缩,不够就换行 */
min-width: 0;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
text-align: center;
}
.responsive-grid .card h4 { margin: 0 0 8px; color: #1e293b; }
.responsive-grid .card p { margin: 0; color: #64748b; font-size: 13px; }
</style>
<div class="responsive-grid">
<div class="card"><h4>卡片 1</h4><p>flex: 1 1 200px</p></div>
<div class="card"><h4>卡片 2</h4><p>当容器宽度不足时自动换行</p></div>
<div class="card"><h4>卡片 3</h4><p>自适应列数</p></div>
<div class="card"><h4>卡片 4</h4><p>无需媒体查询</p></div>
<div class="card"><h4>卡片 5</h4><p>真正的弹性布局</p></div>
</div>
<p style="margin-top:8px;color:#666;font-size:13px;">
↑ 调整容器宽度,卡片会自动换行且保持自适应。无需任何媒体查询。
</p>
</div>原理:当容器宽度足够容纳 N 个 200px 的项目时,它们在一行中均匀分布(flex-grow)。当容器变窄,每行放不下时,最后一个项目换到下一行。
第二章:Grid 详解
CSS Grid Layout 是 CSS 中第一个真正的二维布局系统。它可以同时处理行和列,在宏观的页面级布局中表现出色。
2.1 基础概念
列1 列2 列3
┌──────────┬──────────┬──────────┐
行1 │ grid- │ grid- │ grid- │
│ cell │ cell │ cell │
├──────────┼──────────┼──────────┤
行2 │ grid- │ grid- │ grid- │
│ cell │ cell │ cell │
├──────────┼──────────┼──────────┤
行3 │ grid- │ grid- │ grid- │
│ cell │ cell │ cell │
└──────────┴──────────┴──────────┘核心术语:
- 网格容器(grid container):
display: grid - 网格项目(grid item):容器的直接子元素
- 网格线(grid line):水平和垂直的分割线,用于定位
- 网格单元格(grid cell):最小的网格单位
- 网格区域(grid area):由任意数量的相邻单元格组成
- 网格轨道(grid track):一行或一列
2.2 容器属性
2.2.1 grid-template-columns / grid-template-rows
定义网格的列轨道和行轨道的大小和数量。
.container {
display: grid;
grid-template-columns: 200px 1fr 1fr; /* 3 列:固定 200px + 两个弹性列 */
grid-template-rows: 100px auto 100px; /* 3 行 */
}<div class="demo-box">
<style>
.grid-basic {
display: grid;
grid-template-columns: 150px 1fr 1fr;
grid-template-rows: 60px 120px 60px;
gap: 8px;
padding: 12px;
background: #f0f4f8;
border-radius: 8px;
}
.grid-basic .cell {
background: #3b82f6;
color: white;
padding: 12px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
}
.grid-basic .cell:nth-child(2) { background: #10b981; }
.grid-basic .cell:nth-child(3) { background: #f59e0b; }
.grid-basic .cell:nth-child(4) { background: #ef4444; }
.grid-basic .cell:nth-child(5) { background: #8b5cf6; }
.grid-basic .cell:nth-child(6) { background: #ec4899; }
.grid-basic .cell:nth-child(7) { background: #06b6d4; }
.grid-basic .cell:nth-child(8) { background: #14b8a6; }
.grid-basic .cell:nth-child(9) { background: #eab308; }
</style>
<div class="grid-basic">
<div class="cell">150px</div>
<div class="cell">1fr</div>
<div class="cell">1fr</div>
<div class="cell">150px</div>
<div class="cell">1fr</div>
<div class="cell">1fr</div>
<div class="cell">150px</div>
<div class="cell">1fr</div>
<div class="cell">1fr</div>
</div>
<p style="margin-top:8px;color:#666;font-size:13px;">↑ 150px + 1fr + 1fr,3×3 网格</p>
</div>2.2.2 grid-template-areas
这是 Grid 最强大的特性之一,让你可以用命名区域的方式定义布局。
.container {
display: grid;
grid-template-columns: 200px 1fr 200px;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header"
"sidebar main aside"
"footer footer footer";
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.aside { grid-area: aside; }
.footer { grid-area: footer; }规则:
- 每一行字符串代表网格的一行
- 每个字符串内的名称对应列
- 用英文句号
.表示空单元格 - 同一区域的名称必须形成矩形,不能是 L 形
<div class="demo-box">
<style>
.grid-areas {
display: grid;
grid-template-columns: 160px 1fr 160px;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header"
"sidebar main aside"
"footer footer footer";
gap: 8px;
min-height: 350px;
padding: 12px;
background: #f0f4f8;
border-radius: 8px;
}
.grid-areas .header { grid-area: header; background: #1e293b; color: white; padding: 16px; text-align: center; border-radius: 4px; }
.grid-areas .sidebar { grid-area: sidebar; background: #f59e0b; color: white; padding: 16px; border-radius: 4px; }
.grid-areas .main { grid-area: main; background: #f8fafc; padding: 16px; border-radius: 4px; }
.grid-areas .aside { grid-area: aside; background: #10b981; color: white; padding: 16px; border-radius: 4px; }
.grid-areas .footer { grid-area: footer; background: #1e293b; color: white; padding: 16px; text-align: center; border-radius: 4px; }
</style>
<div class="grid-areas">
<div class="header">📰 Header(跨越三列)</div>
<div class="sidebar">Sidebar 160px</div>
<div class="main"><strong>Main 弹性区</strong><p style="color:#475569;margin-top:8px;">使用 grid-template-areas 让布局一目了然。</p></div>
<div class="aside">Aside 160px</div>
<div class="footer">© Footer(跨越三列)</div>
</div>
</div>2.2.3 gap
同 Flexbox 一样,Grid 也使用 gap 设置轨道间距。
.container {
display: grid;
gap: 16px; /* 行和列间距都为 16px */
/* row-gap: 16px; */ /* 仅行间距 */
/* column-gap: 24px; */ /* 仅列间距 */
}2.2.4 justify-items / align-items
| 属性 | 作用范围 | 效果 |
|---|---|---|
justify-items |
所有项目在行内轴(水平)上的默认对齐方式 | start / end / center / stretch |
align-items |
所有项目在块轴(垂直)上的默认对齐方式 | start / end / center / stretch |
.container {
justify-items: center; /* 所有项目水平居中 */
align-items: center; /* 所有项目垂直居中 */
}2.2.5 justify-content / align-content
当网格的总大小小于容器时,用于控制整个网格在容器内的对齐。
.container {
display: grid;
grid-template-columns: 100px 100px 100px;
justify-content: center; /* 整个网格在容器水平居中 */
align-content: center; /* 整个网格在容器垂直居中 */
}可选值:start、end、center、stretch、space-between、space-around、space-evenly。
2.3 项目属性
2.3.1 grid-column / grid-row
控制项目在网格中的位置和跨度。
/* 使用行号 */
.item {
grid-column: 1 / 3; /* 从第 1 列线到第 3 列线(跨 2 列) */
grid-row: 1 / 2; /* 从第 1 行线到第 2 行线(跨 1 行) */
}
/* 使用 span 关键字(推荐,更直观) */
.item {
grid-column: 1 / span 2; /* 从第 1 列开始,跨越 2 列 */
grid-row: 1 / span 1; /* 从第 1 行开始,跨越 1 行 */
}
/* 简写 */
.item {
grid-column: span 2; /* 不指定起点,自动放置并跨 2 列 */
}<div class="demo-box">
<style>
.grid-span {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
padding: 12px;
background: #f0f4f8;
border-radius: 8px;
}
.grid-span .cell {
background: #3b82f6;
color: white;
padding: 20px;
border-radius: 4px;
text-align: center;
font-size: 13px;
}
.grid-span .cell.featured {
background: #8b5cf6;
grid-column: span 2;
grid-row: span 2;
}
.grid-span .cell.wide {
background: #10b981;
grid-column: span 3;
}
</style>
<div class="grid-span">
<div class="cell">1</div>
<div class="cell featured">⭐ span 2×2</div>
<div class="cell">3</div>
<div class="cell">4</div>
<div class="cell wide">5 — span 3 列</div>
<div class="cell">6</div>
<div class="cell">7</div>
<div class="cell">8</div>
</div>
<p style="margin-top:8px;color:#666;font-size:13px;">
↑ 紫色项目使用 grid-column: span 2; grid-row: span 2。绿色项目使用 grid-column: span 3。
</p>
</div>2.3.2 grid-area
可以用作 grid-row-start / grid-column-start / grid-row-end / grid-column-end 的简写,也可以引用 grid-template-areas 定义的名称。
/* 简写形式:row-start / column-start / row-end / column-end */
.item {
grid-area: 1 / 2 / 3 / 4; /* 行1线到行3线,列2线到列4线 */
}
/* 引用命名区域 */
.item {
grid-area: header;
}2.3.3 justify-self / align-self
覆盖容器上 justify-items 和 align-items 的单个项目设置。
.item {
justify-self: end; /* 这个项目在单元格内靠右 */
align-self: center; /* 这个项目在单元格内垂直居中 */
}2.4 关键单位与函数
2.4.1 fr 单位
fr(fraction)是 Grid 特有的弹性单位,代表可用空间的一个分数。
/* 2:1 比例的两列布局 */
.container {
display: grid;
grid-template-columns: 2fr 1fr; /* 左列占 2/3,右列占 1/3 */
}fr 与百分比的关键区别:fr 在 gap 和 fixed 轨道分配后才计算,不会溢出。
/* 百分比方式:100% + gap 会导致溢出 */
grid-template-columns: 50% 50%; /* 如果 gap:16px,会溢出 */
/* fr 方式:自动扣除 gap 的空间 */
grid-template-columns: 1fr 1fr; /* 100% - 16px 后等分,安全 */2.4.2 repeat() 函数
repeat() 简化重复轨道定义,支持多种模式。
/* 等价于 grid-template-columns: 1fr 1fr 1fr */
.container {
grid-template-columns: repeat(3, 1fr);
}
/* 重复多种模式 */
.container {
grid-template-columns: repeat(2, 200px 1fr); /* 200px 1fr 200px 1fr */
}2.4.3 minmax() 函数
minmax(min, max) 设置轨道的最小值和最大值,是实现响应式的关键。
.container {
grid-template-columns: repeat(3, minmax(200px, 1fr));
/* 每列最小 200px,最大 1fr 弹性 */
}2.4.4 auto-fill vs auto-fit
这是最令人困惑又最强大的一对属性。
/* auto-fill: 尽可能多地创建轨道(即使为空) */
.container {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
/* auto-fit: 收缩空轨道为 0,让有内容的项目扩展 */
.container {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}关键区别:当项目数量不足以填满一行时:
auto-fill保留空轨道的位置,整体布局不变auto-fit将空轨道折叠,剩余项目拉伸填满容器
<div class="demo-box">
<style>
.auto-demo {
display: grid;
gap: 12px;
padding: 12px;
background: #f0f4f8;
border-radius: 8px;
margin-bottom: 12px;
}
.auto-demo .cell {
background: #3b82f6;
color: white;
padding: 24px;
border-radius: 4px;
text-align: center;
font-size: 14px;
}
</style>
<p style="font-weight:600;margin:0 0 4px;">auto-fill(保留空轨道):</p>
<div class="auto-demo" style="grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));">
<div class="cell">A</div>
<div class="cell">B</div>
<div class="cell">C</div>
</div>
<p style="font-weight:600;margin:16px 0 4px;">auto-fit(折叠空轨道):</p>
<div class="auto-demo" style="grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));">
<div class="cell">A</div>
<div class="cell">B</div>
<div class="cell">C</div>
</div>
<p style="margin-top:8px;color:#666;font-size:13px;">
↑ 在同样宽度的容器下,auto-fill 保留空轨道空间,auto-fit 将空轨道折叠使项目扩展。
</p>
</div>2.4.5 fit-content()
fit-content(limit) 使轨道的大小不超过指定值,但可以小于该值(根据内容)。
.container {
grid-template-columns: fit-content(300px) 1fr;
/* 左侧列最大 300px,但会根据内容自适应变小 */
}2.4.6 clamp()
clamp(MIN, PREFERRED, MAX) 不是 Grid 专属,但与响应式布局结合非常好。
font-size: clamp(1rem, 2.5vw, 1.5rem);
/* 最小 1rem,首选 2.5vw,最大 1.5rem */在 Grid 中:
.container {
grid-template-columns: repeat(auto-fit, minmax(clamp(150px, 20vw, 300px), 1fr));
}2.5 命名网格线与命名区域
除了数字索引,网格线还可以有名称。
.container {
display: grid;
grid-template-columns: [sidebar-start] 200px [sidebar-end main-start] 1fr [main-end];
grid-template-rows: [header] auto [content] 1fr [footer] auto;
}
.sidebar {
grid-column: sidebar-start / sidebar-end;
grid-row: header / footer;
}
.main {
grid-column: main-start / main-end;
grid-row: content;
}repeat() 中的自动命名:
.container {
grid-template-columns: repeat(4, [col] 1fr);
/* 自动生成: col col col col (实际上是 col-1, col-2, col-3, col-4) */
}2.6 隐式网格
当你放置的项目超出定义的显式网格范围时,Grid 会自动创建隐式轨道。
.container {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 显式:3 列 */
grid-template-rows: 100px 100px; /* 显式:2 行 */
/* 隐式轨道的尺寸控制 */
grid-auto-rows: 80px; /* 隐式行高度 80px */
grid-auto-columns: 1fr; /* 隐式列宽度 1fr */
grid-auto-flow: dense; /* 自动填充空白(密集模式) */
}grid-auto-flow 的三个值:
| 值 | 行为 |
|---|---|
row(默认) |
按行填充,先填满第一行再填下一行 |
column |
按列填充,先填满第一列再填下一列 |
dense |
尝试填充前面留下的空白(可能改变 DOM 顺序) |
dense的警示:使用dense可能会使项目的视觉顺序与 DOM 顺序不一致,影响 Tab 导航和屏幕阅读器顺序。在无障碍要求高的场景中谨慎使用。
2.7 subgrid
subgrid 让子网格继承父网格的轨道定义,而不是重新计算自己的轨道。目前浏览器支持有限(Firefox 71+、Chrome 117+、Safari 16+)。
.parent {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
}
.child {
display: grid;
grid-column: span 4;
grid-template-columns: subgrid; /* 继承父级的 4 列 */
}用途:当父网格和子网格需要对齐时,subgrid 能确保轨道完全一致。
第三章:Flexbox vs Grid 对比
3.1 核心区别
| 维度 | Flexbox | Grid |
|---|---|---|
| 维度 | 一维(行或列) | 二维(行和列同时) |
| 适用场景 | 组件级布局、工具栏、导航 | 页面级布局、复杂网格 |
| 内容驱动 | 是(项目大小决定排列) | 否(轨道定义决定排列) |
| 重叠 | 不支持 | 支持(同一单元格多个项目) |
| 对齐能力 | 主轴 + 交叉轴 | 行轴 + 块轴,更精细 |
| 响应式 | flex-wrap + flex-basis | auto-fit/auto-fill + minmax |
3.2 何时用 Flexbox
✅ 一维排列:导航栏、标签页、按钮组、工具条
✅ 内容大小决定布局:卡片底部对齐、头像+文字组合
✅ 简单居中:垂直居中、水平居中
✅ 组件内部:按钮内的图标+文字、input 前置图标
/* 导航栏——Flexbox 的天然场景 */
.nav {
display: flex;
gap: 8px;
align-items: center;
}
.nav .spacer {
margin-left: auto; /* 推到右边 */
}3.3 何时用 Grid
✅ 二维布局:页面整体结构、仪表盘、照片墙
✅ 行列对齐:表格状数据展示、产品列表、卡片网格
✅ 显式行+列控制:圣杯布局、侧边栏+页头的复杂结构
✅ 项目重叠:装饰层叠效果、封面图+标题叠加
/* 页面布局——Grid 的强项 */
.page {
display: grid;
grid-template-columns: 220px 1fr;
grid-template-rows: 60px 1fr 40px;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
height: 100vh;
}3.4 混合使用的黄金法则
页面用 Grid,组件用 Flexbox。Grid 负责宏观,Flexbox 负责微观。
/* 页面框架:Grid */
.app-layout {
display: grid;
grid-template-columns: 240px 1fr;
grid-template-rows: 64px 1fr;
grid-template-areas:
"sidebar header"
"sidebar main";
height: 100vh;
}
/* 组件内部:Flexbox */
.toolbar {
grid-area: header;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
}
.card-list {
grid-area: main;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
padding: 24px;
}
.card {
display: flex;
flex-direction: column;
/* ... */
}
.card-footer {
display: flex;
gap: 8px;
margin-top: auto; /* 推到底部 */
}第四章:经典布局实战
4.1 响应式导航栏(Flexbox)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>响应式导航栏 - Flexbox</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
height: 60px;
background: #1e293b;
color: white;
}
.navbar .logo {
font-size: 20px;
font-weight: 700;
letter-spacing: 1px;
}
.navbar .nav-links {
display: flex;
gap: 4px;
list-style: none;
}
.navbar .nav-links a {
color: #cbd5e1;
text-decoration: none;
padding: 8px 16px;
border-radius: 6px;
transition: background 0.2s;
font-size: 14px;
}
.navbar .nav-links a:hover {
background: #334155;
color: white;
}
.navbar .nav-actions {
display: flex;
gap: 8px;
align-items: center;
}
.navbar .btn {
padding: 8px 16px;
border-radius: 6px;
border: none;
font-size: 14px;
cursor: pointer;
}
.navbar .btn-login {
background: transparent;
color: #cbd5e1;
border: 1px solid #475569;
}
.navbar .btn-register {
background: #3b82f6;
color: white;
}
/* 移动端:导航链接折行 */
@media (max-width: 640px) {
.navbar { flex-wrap: wrap; height: auto; padding: 12px 16px; gap: 8px; }
.navbar .nav-links { order: 3; width: 100%; justify-content: center; }
}
</style>
</head>
<body>
<nav class="navbar">
<div class="logo">🔧 FlexNav</div>
<ul class="nav-links">
<li><a href="#">首页</a></li>
<li><a href="#">产品</a></li>
<li><a href="#">定价</a></li>
<li><a href="#">关于</a></li>
</ul>
<div class="nav-actions">
<button class="btn btn-login">登录</button>
<button class="btn btn-register">注册</button>
</div>
</nav>
</body>
</html>4.2 卡片网格(Grid auto-fill)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>卡片网格 - Grid</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
padding: 32px;
background: #f1f5f9;
}
.page-title {
font-size: 28px;
margin-bottom: 24px;
color: #1e293b;
}
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
}
.card {
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
}
.card-image {
width: 100%;
height: 180px;
object-fit: cover;
background: linear-gradient(135deg, #667eea, #764ba2);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 40px;
}
.card-body {
padding: 20px;
}
.card-body h3 {
font-size: 18px;
margin-bottom: 8px;
color: #1e293b;
}
.card-body p {
font-size: 14px;
color: #64748b;
line-height: 1.6;
}
.card-tags {
display: flex;
gap: 6px;
margin-top: 12px;
flex-wrap: wrap;
}
.card-tags span {
background: #e2e8f0;
padding: 4px 10px;
border-radius: 20px;
font-size: 12px;
color: #475569;
}
</style>
</head>
<body>
<h1 class="page-title">📸 卡片网格</h1>
<div class="card-grid">
<div class="card">
<div class="card-image">🌄</div>
<div class="card-body">
<h3>山河壮丽</h3>
<p>使用 Grid auto-fill + minmax 实现自适应卡片网格布局。</p>
<div class="card-tags"><span>Grid</span><span>响应式</span></div>
</div>
</div>
<div class="card">
<div class="card-image">🌊</div>
<div class="card-body">
<h3>海洋之韵</h3>
<p>无需任何媒体查询即可在不同屏幕宽度下自动调整列数。</p>
<div class="card-tags"><span>auto-fill</span><span>minmax</span></div>
</div>
</div>
<div class="card">
<div class="card-image">🏔️</div>
<div class="card-body">
<h3>雪山之巅</h3>
<p>每个卡片的内容独立,高度由内容撑开,但网格保持整齐对齐。</p>
<div class="card-tags"><span>Flex</span><span>卡片</span></div>
</div>
</div>
<div class="card">
<div class="card-image">🌲</div>
<div class="card-body">
<h3>森林秘境</h3>
<p>hover 时上浮效果,box-shadow 增强,提升交互感。</p>
<div class="card-tags"><span>动画</span><span>交互</span></div>
</div>
</div>
<div class="card">
<div class="card-image">🏜️</div>
<div class="card-body">
<h3>大漠孤烟</h3>
<p>每组卡片均可独立添加标签,使用 Flexbox 包裹排列。</p>
<div class="card-tags"><span>标签</span><span>flex-wrap</span></div>
</div>
</div>
<div class="card">
<div class="card-image">🌋</div>
<div class="card-body">
<h3>火山奇观</h3>
<p>20px gap 间距,自动填充网格,优雅的现代布局方案。</p>
<div class="card-tags"><span>gap</span><span>Grid</span></div>
</div>
</div>
</div>
</body>
</html>4.3 圣杯布局(Grid template areas)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>圣杯布局 - Grid</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.holy-grail {
display: grid;
grid-template-columns: 200px 1fr 200px;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header"
"left main right"
"footer footer footer";
min-height: 100vh;
gap: 0;
}
header {
grid-area: header;
background: #1e293b;
color: white;
padding: 20px 24px;
display: flex;
align-items: center;
justify-content: space-between;
}
header h1 { font-size: 20px; }
header nav { display: flex; gap: 16px; }
header nav a { color: #94a3b8; text-decoration: none; font-size: 14px; }
header nav a:hover { color: white; }
.left-sidebar {
grid-area: left;
background: #f8fafc;
padding: 24px;
border-right: 1px solid #e2e8f0;
}
.left-sidebar h3 { font-size: 14px; color: #94a3b8; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 12px; }
.left-sidebar ul { list-style: none; }
.left-sidebar ul li { padding: 8px 0; color: #475569; border-bottom: 1px solid #e2e8f0; font-size: 14px; cursor: pointer; }
.left-sidebar ul li:hover { color: #3b82f6; }
main {
grid-area: main;
padding: 32px;
background: white;
}
main h2 { font-size: 24px; color: #1e293b; margin-bottom: 16px; }
main p { color: #475569; line-height: 1.8; font-size: 15px; margin-bottom: 16px; }
main code { background: #f1f5f9; padding: 2px 6px; border-radius: 4px; font-size: 13px; color: #ef4444; }
.right-sidebar {
grid-area: right;
background: #f8fafc;
padding: 24px;
border-left: 1px solid #e2e8f0;
}
.right-sidebar .card { background: white; border-radius: 8px; padding: 16px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
.right-sidebar .card h4 { font-size: 14px; color: #1e293b; margin-bottom: 4px; }
.right-sidebar .card p { font-size: 12px; color: #64748b; }
footer {
grid-area: footer;
background: #1e293b;
color: #94a3b8;
text-align: center;
padding: 16px;
font-size: 13px;
}
/* 响应式:窄屏时转为单栏 */
@media (max-width: 768px) {
.holy-grail {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"left"
"right"
"footer";
}
}
</style>
</head>
<body>
<div class="holy-grail">
<header>
<h1>📖 Grid 圣杯</h1>
<nav>
<a href="#">首页</a>
<a href="#">文档</a>
<a href="#">博客</a>
</nav>
</header>
<aside class="left-sidebar">
<h3>目录</h3>
<ul>
<li>Grid 基础</li>
<li>template areas</li>
<li>响应式设计</li>
<li>常见模式</li>
<li>实战案例</li>
</ul>
</aside>
<main>
<h2>Grid-template-areas 圣杯布局</h2>
<p>
这是使用 CSS Grid 的 <code>grid-template-areas</code> 实现的经典圣杯布局。
header 跨越 3 列,中间区域分为左 sidebar、main、右 sidebar 三部分,footer 横跨底部。
</p>
<p>
在窄屏(< 768px)下,通过媒体查询重新定义 <code>grid-template-areas</code>,
将布局切换为单栏模式,完美适应移动端。
</p>
<p>
这种方式的优势是:布局的逻辑结构一目了然,修改布局只需要调整 <code>grid-template-areas</code> 的字符串,
无需改动任何项目的位置代码。
</p>
</main>
<aside class="right-sidebar">
<div class="card">
<h4>📌 推荐阅读</h4>
<p>CSS Grid 完全指南</p>
</div>
<div class="card">
<h4>🔗 相关资源</h4>
<p>Flexbox 小游戏<br>Grid 花园</p>
</div>
</aside>
<footer>© 2024 CSS 布局深度指南 · 使用 Grid 圣杯布局</footer>
</div>
</body>
</html>4.4 侧边栏 + 主体(Grid + Flexbox 混合)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>侧边栏 + 主体 - 混合布局</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
/* Grid:页面框架 */
.app-layout {
display: grid;
grid-template-columns: 240px 1fr;
grid-template-rows: 60px 1fr;
grid-template-areas:
"sidebar header"
"sidebar main";
min-height: 100vh;
}
.sidebar {
grid-area: sidebar;
background: #1e293b;
color: white;
padding: 20px 0;
display: flex;
flex-direction: column; /* Flexbox:侧边栏内部 */
}
.sidebar .logo {
padding: 0 20px 20px;
font-size: 18px;
font-weight: 700;
border-bottom: 1px solid #334155;
display: flex;
align-items: center;
gap: 8px;
}
.sidebar .nav-section {
padding: 20px;
}
.sidebar .nav-section .label {
font-size: 11px;
text-transform: uppercase;
color: #64748b;
letter-spacing: 1px;
margin-bottom: 8px;
}
.sidebar .nav-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
border-radius: 8px;
color: #94a3b8;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.sidebar .nav-item:hover,
.sidebar .nav-item.active {
background: #334155;
color: white;
}
.sidebar .spacer {
flex: 1; /* Flexbox:撑开剩余空间 */
}
.sidebar .user-info {
padding: 16px 20px;
border-top: 1px solid #334155;
display: flex;
align-items: center;
gap: 10px;
font-size: 13px;
}
header {
grid-area: header;
background: white;
border-bottom: 1px solid #e2e8f0;
padding: 0 24px;
display: flex;
align-items: center;
justify-content: space-between;
}
header .search {
display: flex;
align-items: center;
gap: 8px;
background: #f1f5f9;
padding: 8px 16px;
border-radius: 8px;
}
header .search input {
border: none;
background: transparent;
outline: none;
font-size: 14px;
color: #475569;
}
header .user-actions {
display: flex;
gap: 12px;
align-items: center;
}
header .avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: #3b82f6;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 14px;
}
main {
grid-area: main;
padding: 24px;
background: #f8fafc;
overflow-y: auto;
}
/* Flexbox:工具栏内部 */
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
}
.toolbar h2 { font-size: 22px; color: #1e293b; }
.toolbar .actions { display: flex; gap: 8px; }
.toolbar .actions button {
padding: 8px 16px;
border-radius: 6px;
border: none;
font-size: 13px;
cursor: pointer;
}
.toolbar .btn-primary { background: #3b82f6; color: white; }
.toolbar .btn-secondary { background: white; border: 1px solid #e2e8f0; color: #475569; }
/* Grid:仪表盘卡片网格 */
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 16px;
}
.dashboard-grid .card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
}
.dashboard-grid .card .stat-label {
font-size: 12px;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 1px;
}
.dashboard-grid .card .stat-value {
font-size: 32px;
font-weight: 700;
color: #1e293b;
margin: 4px 0;
}
.dashboard-grid .card .stat-change {
font-size: 12px;
display: flex;
align-items: center;
gap: 4px;
}
.stat-change.up { color: #10b981; }
.stat-change.down { color: #ef4444; }
</style>
</head>
<body>
<div class="app-layout">
<aside class="sidebar">
<div class="logo">⬡ Dashboard</div>
<div class="nav-section">
<div class="label">导航菜单</div>
<div class="nav-item active">🏠 总览</div>
<div class="nav-item">📊 分析</div>
<div class="nav-item">📁 项目</div>
<div class="nav-item">👥 团队</div>
<div class="nav-item">⚙️ 设置</div>
</div>
<div class="spacer"></div>
<div class="user-info">
<div class="avatar">张</div>
<div>
<div style="font-size:13px;color:white;">张三</div>
<div style="font-size:11px;color:#64748b;">zhang@example.com</div>
</div>
</div>
</aside>
<header>
<div class="search">
<span>🔍</span>
<input type="text" placeholder="搜索...">
</div>
<div class="user-actions">
<span>🔔</span>
<div class="avatar">张</div>
</div>
</header>
<main>
<div class="toolbar">
<h2>总览</h2>
<div class="actions">
<button class="btn-secondary">📤 导出</button>
<button class="btn-primary">➕ 新建</button>
</div>
</div>
<div class="dashboard-grid">
<div class="card">
<div class="stat-label">总用户</div>
<div class="stat-value">12,847</div>
<div class="stat-change up">↑ 12.5% 较上月</div>
</div>
<div class="card">
<div class="stat-label">活跃用户</div>
<div class="stat-value">4,291</div>
<div class="stat-change up">↑ 8.2% 较上月</div>
</div>
<div class="card">
<div class="stat-label">收入</div>
<div class="stat-value">¥89,240</div>
<div class="stat-change up">↑ 23.1% 较上月</div>
</div>
<div class="card">
<div class="stat-label">退出率</div>
<div class="stat-value">3.2%</div>
<div class="stat-change down">↓ 0.8% 较上月</div>
</div>
<div class="card">
<div class="stat-label">页面访问</div>
<div class="stat-value">45.2K</div>
<div class="stat-change up">↑ 15.3% 较上月</div>
</div>
<div class="card">
<div class="stat-label">平均时长</div>
<div class="stat-value">4m 32s</div>
<div class="stat-change up">↑ 5.2% 较上月</div>
</div>
</div>
</main>
</div>
</body>
</html>4.5 仪表盘布局(命名区域 + 隐式网格)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>仪表盘 - 命名区域 + 隐式网格</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
background: #0f172a;
color: white;
padding: 24px;
}
.dashboard {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-template-rows: auto auto auto;
grid-template-areas:
"kpi1 kpi2 kpi3 kpi4"
"chart chart chart activity"
"table table table activity";
gap: 16px;
max-width: 1400px;
margin: 0 auto;
}
/* 命名区域 */
.kpi1 { grid-area: kpi1; }
.kpi2 { grid-area: kpi2; }
.kpi3 { grid-area: kpi3; }
.kpi4 { grid-area: kpi4; }
.chart { grid-area: chart; }
.table { grid-area: table; }
.activity { grid-area: activity; }
/* 通用卡片样式 */
.dashboard > div {
background: #1e293b;
border-radius: 12px;
padding: 20px;
border: 1px solid #334155;
}
/* KPI 卡片 — 内部用 Flexbox */
.kpi-card {
display: flex;
flex-direction: column;
}
.kpi-card .kpi-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.kpi-card .kpi-label {
font-size: 13px;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 1px;
}
.kpi-card .kpi-icon {
font-size: 24px;
opacity: 0.6;
}
.kpi-card .kpi-value {
font-size: 28px;
font-weight: 700;
margin-bottom: 4px;
}
.kpi-card .kpi-change {
font-size: 12px;
display: flex;
align-items: center;
gap: 4px;
}
.kpi-change.positive { color: #10b981; }
.kpi-change.negative { color: #ef4444; }
.chart-area {
display: flex;
flex-direction: column;
}
.chart-area .chart-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 12px;
}
.chart-area .chart-bars {
display: flex;
align-items: flex-end;
gap: 12px;
height: 180px;
flex: 1;
}
.chart-area .bar {
flex: 1;
background: linear-gradient(to top, #3b82f6, #8b5cf6);
border-radius: 4px 4px 0 0;
min-height: 20px;
transition: height 0.5s ease;
position: relative;
}
.chart-area .bar-label {
text-align: center;
font-size: 11px;
color: #64748b;
margin-top: 8px;
}
/* 隐式网格区域:如果有更多 kpi,会自动放在隐式行中 */
.dashboard .more-card {
background: #1e293b;
border-radius: 12px;
padding: 20px;
border: 1px solid #334155;
}
.activity-area { display: flex; flex-direction: column; }
.activity-area .activity-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
}
.activity-item {
display: flex;
gap: 12px;
align-items: flex-start;
padding: 10px 0;
border-bottom: 1px solid #334155;
}
.activity-item:last-child { border-bottom: none; }
.activity-dot {
width: 8px;
height: 8px;
border-radius: 50%;
margin-top: 6px;
flex-shrink: 0;
}
.activity-dot.blue { background: #3b82f6; }
.activity-dot.green { background: #10b981; }
.activity-dot.purple { background: #8b5cf6; }
.activity-dot.orange { background: #f59e0b; }
.activity-text { font-size: 13px; color: #cbd5e1; }
.activity-time { font-size: 11px; color: #64748b; margin-top: 2px; }
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.data-table th {
text-align: left;
padding: 10px 8px;
color: #64748b;
font-weight: 500;
border-bottom: 1px solid #334155;
}
.data-table td {
padding: 10px 8px;
border-bottom: 1px solid #1e293b;
color: #cbd5e1;
}
.data-table .status {
padding: 2px 8px;
border-radius: 20px;
font-size: 11px;
}
.status.active { background: rgba(16,185,129,0.15); color: #10b981; }
.status.pending { background: rgba(245,158,11,0.15); color: #f59e0b; }
.status.closed { background: rgba(100,116,139,0.15); color: #94a3b8; }
</style>
</head>
<body>
<div class="dashboard">
<!-- KPI 行(4 列) -->
<div class="kpi1 kpi-card">
<div class="kpi-header">
<span class="kpi-label">总收入</span>
<span class="kpi-icon">💰</span>
</div>
<div class="kpi-value">$128,430</div>
<div class="kpi-change positive">↑ 16.8% 较上月</div>
</div>
<div class="kpi2 kpi-card">
<div class="kpi-header">
<span class="kpi-label">新订单</span>
<span class="kpi-icon">📦</span>
</div>
<div class="kpi-value">1,247</div>
<div class="kpi-change positive">↑ 8.3% 较上月</div>
</div>
<div class="kpi3 kpi-card">
<div class="kpi-header">
<span class="kpi-label">转化率</span>
<span class="kpi-icon">📈</span>
</div>
<div class="kpi-value">3.42%</div>
<div class="kpi-change positive">↑ 0.8% 较上月</div>
</div>
<div class="kpi4 kpi-card">
<div class="kpi-header">
<span class="kpi-label">活跃用户</span>
<span class="kpi-icon">👥</span>
</div>
<div class="kpi-value">8,921</div>
<div class="kpi-change negative">↓ 2.1% 较上月</div>
</div>
<!-- 图表区:跨 3 列 -->
<div class="chart chart-area">
<div class="chart-title">📊 月收入趋势</div>
<div style="display:flex;flex-direction:column;flex:1;">
<div class="chart-bars">
<div style="display:flex;flex-direction:column;flex:1;align-items:center;">
<div class="bar" style="height:40%;"></div>
<div class="bar-label">1月</div>
</div>
<div style="display:flex;flex-direction:column;flex:1;align-items:center;">
<div class="bar" style="height:55%;"></div>
<div class="bar-label">2月</div>
</div>
<div style="display:flex;flex-direction:column;flex:1;align-items:center;">
<div class="bar" style="height:45%;"></div>
<div class="bar-label">3月</div>
</div>
<div style="display:flex;flex-direction:column;flex:1;align-items:center;">
<div class="bar" style="height:65%;"></div>
<div class="bar-label">4月</div>
</div>
<div style="display:flex;flex-direction:column;flex:1;align-items:center;">
<div class="bar" style="height:70%;"></div>
<div class="bar-label">5月</div>
</div>
<div style="display:flex;flex-direction:column;flex:1;align-items:center;">
<div class="bar" style="height:85%;"></div>
<div class="bar-label">6月</div>
</div>
<div style="display:flex;flex-direction:column;flex:1;align-items:center;">
<div class="bar" style="height:95%;"></div>
<div class="bar-label">7月</div>
</div>
<div style="display:flex;flex-direction:column;flex:1;align-items:center;">
<div class="bar" style="height:100%;"></div>
<div class="bar-label">8月</div>
</div>
</div>
</div>
</div>
<!-- 活动日志:跨 1 列 -->
<div class="activity activity-area">
<div class="activity-title">⚡ 最新动态</div>
<div class="activity-item">
<div class="activity-dot blue"></div>
<div>
<div class="activity-text">新用户 @zhangsan 注册</div>
<div class="activity-time">2 分钟前</div>
</div>
</div>
<div class="activity-item">
<div class="activity-dot green"></div>
<div>
<div class="activity-text">订单 #ORD-3421 已完成</div>
<div class="activity-time">15 分钟前</div>
</div>
</div>
<div class="activity-item">
<div class="activity-dot purple"></div>
<div>
<div class="activity-text">项目「新官网」部署成功</div>
<div class="activity-time">1 小时前</div>
</div>
</div>
<div class="activity-item">
<div class="activity-dot orange"></div>
<div>
<div class="activity-text">系统更新至 v2.4.0</div>
<div class="activity-time">3 小时前</div>
</div>
</div>
</div>
<!-- 数据表格:跨 3 列 -->
<div class="table">
<div style="font-size:16px;font-weight:600;margin-bottom:12px;">📋 最新订单</div>
<table class="data-table">
<thead>
<tr>
<th>订单号</th>
<th>客户</th>
<th>金额</th>
<th>状态</th>
<th>日期</th>
</tr>
</thead>
<tbody>
<tr>
<td>#ORD-3421</td>
<td>张三</td>
<td>$2,450</td>
<td><span class="status active">已完成</span></td>
<td>2024-08-27</td>
</tr>
<tr>
<td>#ORD-3420</td>
<td>李四</td>
<td>$840</td>
<td><span class="status pending">处理中</span></td>
<td>2024-08-27</td>
</tr>
<tr>
<td>#ORD-3419</td>
<td>王五</td>
<td>$3,120</td>
<td><span class="status active">已完成</span></td>
<td>2024-08-26</td>
</tr>
<tr>
<td>#ORD-3418</td>
<td>赵六</td>
<td>$560</td>
<td><span class="status closed">已取消</span></td>
<td>2024-08-26</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>第五章:性能与最佳实践
5.1 性能考虑
-
Grid 比 Flexbox 略重:Grid 的二维布局计算比 Flexbox 的一维计算更复杂。在包含数千个项目的超大列表中,Flexbox 的渲染性能略优。
-
避免深层嵌套:无论使用 Flexbox 还是 Grid,过深的嵌套都会增加浏览器的 Reflow 开销。尽量保持布局层级的扁平化。
-
gap优于margin:使用gap而不是margin控制间距,可以减少元素数量的计算复杂度。 -
grid-auto-flow: dense有代价:密集填充模式需要额外的布局计算,在超大网格中可能有性能开销。
5.2 无障碍性
-
不要只依赖视觉顺序:
order和grid-auto-flow: dense会改变视觉顺序而不改变 DOM 顺序,可能导致屏幕阅读器用户的体验混乱。 -
Grid 区域的名称为语义化标签:使用
<header>、<main>、<aside>、<footer>等语义化 HTML 标签,而不是通用的<div>,并结合grid-area使用。 -
保持逻辑焦点顺序:确保 Tab 键的焦点顺序与页面的视觉阅读顺序一致。
5.3 浏览器兼容性
| 特性 | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| Flexbox | ✅ 全支持 | ✅ 全支持 | ✅ 全支持 | ✅ 全支持 |
| Grid | ✅ 57+ | ✅ 52+ | ✅ 10.1+ | ✅ 16+ |
gap in Flex |
✅ 84+ | ✅ 63+ | ✅ 14.1+ | ✅ 84+ |
subgrid |
✅ 117+ | ✅ 71+ | ✅ 16+ | ✅ 117+ |
auto-fill / auto-fit |
✅ 57+ | ✅ 52+ | ✅ 10.1+ | ✅ 16+ |
总结
CSS Flexbox 和 Grid 是现代 Web 布局的两大基石。简单来说:
- Flexbox = 一维、内容驱动、适合组件内部布局
- Grid = 二维、容器驱动、适合页面整体布局
- 最佳实践 = 页面用 Grid,组件用 Flexbox,混合使用发挥最大威力
掌握这两种布局方式,意味着你不再需要 float、inline-block、clearfix 这些历史上"勉强可用"的布局方案。你会发现,你思考布局的方式从"怎么把元素放到正确位置"变成了"我想要什么样的布局效果",这正是 CSS 现代布局革命带给我们的自由。
本文共约 11,000 字,发布于 2024-08-27