Flex & Grid
面试必问的两种现代布局方案。Flex 适合一维排列,Grid 适合二维网格。每个属性都附带视觉效果示意。
Flex 布局
核心概念
┌─────────────────────────────────────────────────────┐
│ Flex Container (display: flex) │
│ │
│ ←─────────────── 主轴 main axis ───────────────→ │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ 1 │ │ 2 │ │ 3 │ ↑ 交叉轴 cross axis │
│ └─────┘ └─────┘ └─────┘ ↓ │
│ │
└─────────────────────────────────────────────────────┘
默认:主轴 = 水平方向(左→右),交叉轴 = 垂直方向(上→下)容器属性
flex-direction — 决定主轴方向
css
.container { display: flex; }| 值 | 主轴方向 | 效果 |
|---|---|---|
row(默认) | 左 → 右 | [1] [2] [3] |
row-reverse | 右 → 左 | [3] [2] [1] |
column | 上 → 下 | 纵向排列 |
column-reverse | 下 → 上 | 纵向逆序 |
css
/* row(默认) */
flex-direction: row;
/*
┌──────────────────────────┐
│ [1] [2] [3] │ ← 从左到右
└──────────────────────────┘
*/
/* column */
flex-direction: column;
/*
┌─────┐
│ [1] │
│ [2] │ ← 从上到下
│ [3] │
└─────┘
*/
/* row-reverse */
flex-direction: row-reverse;
/*
┌──────────────────────────┐
│ [3] [2] [1] │ ← 从右到左,顺序颠倒
└──────────────────────────┘
*/justify-content — 主轴方向对齐
假设容器宽 600px,3 个子项各 100px:
css
/* flex-start(默认):靠左 */
justify-content: flex-start;
/*
┌──────────────────────────────────────────────┐
│ [1] [2] [3] │
└──────────────────────────────────────────────┘
*/
/* flex-end:靠右 */
justify-content: flex-end;
/*
┌──────────────────────────────────────────────┐
│ [1] [2] [3] │
└──────────────────────────────────────────────┘
*/
/* center:居中 */
justify-content: center;
/*
┌──────────────────────────────────────────────┐
│ [1] [2] [3] │
└──────────────────────────────────────────────┘
*/
/* space-between:两端对齐,中间均分 */
justify-content: space-between;
/*
┌──────────────────────────────────────────────┐
│ [1] [2] [3] │
└──────────────────────────────────────────────┘
↑ ↑ ↑
贴左边 正中间 贴右边
*/
/* space-around:每个子项左右 margin 相等 */
justify-content: space-around;
/*
┌──────────────────────────────────────────────┐
│ ■ [1] ■ ■ [2] ■ ■ [3] ■ │
└──────────────────────────────────────────────┘
■ = 等距 margin,相邻子项间距是边缘的 2 倍
*/
/* space-evenly:所有间距相等(含边缘) */
justify-content: space-evenly;
/*
┌──────────────────────────────────────────────┐
│ ■ [1] ■ [2] ■ [3] ■ │
└──────────────────────────────────────────────┘
所有 ■ 间距相等
*/align-items — 交叉轴对齐(单行)
假设容器高 200px:
css
align-items: stretch; /* 默认:拉伸填满 */
/*
┌──────────────────┐
│ ┌──────────────┐ │
│ │ [1] │ │ ← 没设高度 → 撑满容器
│ │ │ │
│ ├──────────────┤ │
│ │ [2] │ │
│ │ │ │
│ └──────────────┘ │
└──────────────────┘
*/
align-items: flex-start; /* 顶部对齐 */
/*
┌──────────────────┐
│ ┌──┐ ┌──┐ ┌──┐ │
│ │1 │ │2 │ │3 │ │ ← 都贴顶部
│ └──┘ └──┘ └──┘ │
│ │
│ │
└──────────────────┘
*/
align-items: flex-end; /* 底部对齐 */
/*
┌──────────────────┐
│ │
│ │
│ ┌──┐ ┌──┐ ┌──┐ │ ← 都贴底部
│ │1 │ │2 │ │3 │ │
│ └──┘ └──┘ └──┘ │
└──────────────────┘
*/
align-items: center; /* 居中 */
/*
┌──────────────────┐
│ │
│ ┌──┐ ┌──┐ ┌──┐ │ ← 垂直居中
│ │1 │ │2 │ │3 │ │
│ └──┘ └──┘ └──┘ │
│ │
└──────────────────┘
*/
align-items: baseline; /* 基线对齐 */
/*
┌────────────────────────┐
│ 大字 小字 12px │ ← 按文字基线对齐
│ ┌──┐ ┌──┐ ┌──┐ │
│ │A │ │b │ │c │ │
│ └──┘ └──┘ └──┘ │
└────────────────────────┘
*/flex-wrap + align-content — 多行对齐
当子项宽度总和超过容器:
css
/* nowrap(默认):不换行,子项等比缩小 */
flex-wrap: nowrap;
/*
┌──────────────────────┐ 容器 400px
│ [浅][深][浅][深][浅] │ 5 个各 100px → 缩小到各 80px
└──────────────────────┘
*/
/* wrap:换行 */
flex-wrap: wrap;
/*
┌──────────────────────┐
│ [1] [2] [3] [4] │ ← 第一行(4 个放得下)
│ [5] │ ← 第二行(放不下,换行)
└──────────────────────┘
*/align-content 控制多行时行与行之间的间距(类似 justify-content 的纵向版):
css
align-content: space-between;
/*
┌──────────────────────┐
│ [1] [2] [3] │ ← 贴顶
│ │
│ [4] [5] │ ← 贴底
└──────────────────────┘
*/
align-content: center;
/*
┌──────────────────────┐
│ │
│ [1] [2] [3] │ ← 整体居中
│ [4] [5] │
│ │
└──────────────────────┘
*/⚠️
align-content只在多行(flex-wrap: wrap)时生效。单行用align-items。
项目属性(子元素)
flex-grow — 瓜分剩余空间
容器 600px,A 和 B 各 100px,剩余 400px:
css
/* A flex-grow:1 B flex-grow:2 */
/* A 分 1/3 剩余 = 133px,最终 100+133 = 233px */
/* B 分 2/3 剩余 = 267px,最终 100+267 = 367px */
/*
┌──────────────────────────────────────────────────┐
│ ┌──────────────────┐ ┌────────────────────────┐ │
│ │ A(233px) │ │ B(367px) │ │
│ └──────────────────┘ └────────────────────────┘ │
└──────────────────────────────────────────────────┘
*/
/* 全设 flex-grow: 1 → 等分 */
/*
┌──────────────────────────────────────────────────┐
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ A(300px) │ │ B(300px) │ │
│ └─────────────────┘ └─────────────────┘ │
└──────────────────────────────────────────────────┘
*/flex-shrink — 空间不够时谁缩
容器 300px,A 200px + B 300px,溢出 200px:
css
/* A flex-shrink:1 B flex-shrink:2 */
/* 总权重 = 1×200 + 2×300 = 800 */
/* A 收缩 200 × 200/800 = 50px → 最终 150px */
/* B 收缩 200 × 600/800 = 150px → 最终 150px */
/*
┌──────────────────────────────────────┐ 容器 300px
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ A(150px) │ │ B(150px) │ │
│ └─────────────┘ └─────────────────┘ │
└──────────────────────────────────────┘
*/
/* A flex-shrink:0 B flex-shrink:1 */
/* A 不缩 = 200px,B 独占剩余 100px */
/*
┌──────────────────────────────────────┐ 容器 300px
│ ┌─────────────────┐ ┌───┐ │
│ │ A(200px 不缩) │ │B │ ← 100px │
│ └─────────────────┘ └───┘ │
└──────────────────────────────────────┘
*/flex 简写 — 最常用的三个值
css
/* flex: 1 = flex-grow:1, flex-shrink:1, flex-basis:0% */
/* 从 0 开始分配 → 不管内容长短,严格等分 */
flex: 1;
/*
┌──────────────────────────────────────┐
│ ┌────────────┐ ┌────────────┐ │
│ │ 1 │ │ 2 │ │ ← 等宽
│ └────────────┘ └────────────┘ │
└──────────────────────────────────────┘
*/
/* flex: auto = flex-grow:1, flex-shrink:1, flex-basis:auto */
/* 先按内容宽度,再分配剩余 → 内容长的更宽 */
flex: auto;
/*
┌──────────────────────────────────────────┐
│ ┌──────┐ ┌───────────────────────────┐ │
│ │ 短 │ │ 长内容长内容长内容长内容 │ │ ← 不等宽
│ └──────┘ └───────────────────────────┘ │
└──────────────────────────────────────────┘
*/
/* flex: none = flex-grow:0, flex-shrink:0, flex-basis:auto */
/* 不放大不缩小,定死 */
flex: none;align-self — 某个子项单独对齐
css
.item2 { align-self: flex-end; }
/*
┌──────────────────────────┐
│ ┌──┐ ┌──┐ │
│ │1 │ │3 │ │
│ └──┘ └──┘ │
│ │
│ ┌──┐ │
│ │2 │ ← 独自沉底
│ └──┘ │
└──────────────────────────┘
*/经典 Flex 布局速查
水平垂直居中(单个子项)
css
.parent {
display: flex;
justify-content: center;
align-items: center;
}
/*
┌─────────────────────────┐
│ │
│ ┌───┐ │
│ │ ● │ │ ← 正中间
│ └───┘ │
│ │
└─────────────────────────┘
*/圣杯布局(header + 三列 + footer)
css
.layout {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.body { display: flex; flex: 1; }
.sidebar { width: 200px; }
.main { flex: 1; }
/*
┌───────────────────────────────────────┐
│ Header │
├──────┬──────────────────────┬─────────┤
│ Left │ Main │ Right │
│200px │ flex:1 │ 200px │
│ │ │ │
├──────┴──────────────────────┴─────────┤
│ Footer │
└───────────────────────────────────────┘
*/等分布局(n 列自适应)
css
.grid { display: flex; }
.grid > * { flex: 1; }
/*
┌──────┬──────┬──────┬──────┐
│ 1 │ 2 │ 3 │ 4 │ ← 严格等宽
└──────┴──────┴──────┴──────┘
*/一固定一自适应(左侧固定,右侧撑满)
css
.sidebar { width: 200px; flex-shrink: 0; }
.content { flex: 1; }
/*
┌───────────┬──────────────────────────┐
│ Sidebar │ Content(剩余宽度) │
│ 200px │ flex: 1 │
│ 不缩 │ │
└───────────┴──────────────────────────┘
*/Grid 布局
核心概念
┌─────────────────────────────────────────────┐
│ Grid Container │
│ 列1 列2 列3 列4 │
│ ┌────────┬────────┬────────┬────────┐ │
│ │ 单元格 │ 单元格 │ 单元格 │ 单元格 │ ← 行1
│ ├────────┼────────┼────────┼────────┤ │
│ │ 单元格 │ 单元格 │ 单元格 │ 单元格 │ ← 行2
│ ├────────┼────────┼────────┼────────┤ │
│ │ 单元格 │ 单元格 │ 单元格 │ 单元格 │ ← 行3
│ └────────┴────────┴────────┴────────┘ │
│ ↑网格线1 ↑网格线2 ↑网格线3 ↑网格线4 │
└─────────────────────────────────────────────┘
核心术语:
网格线(grid line)= 行列分界线,从 1 开始编号
网格轨道(grid track)= 两平行线之间的空间(行 / 列)
单元格(grid cell)= 行列交叉的最小单位
网格区域(grid area)= 多个单元格组成的矩形容器属性
grid-template-columns / grid-template-rows — 定义行列
css
/* 三列:宽 200px | 1/3 剩余 | 1/3 剩余 */
grid-template-columns: 200px 1fr 1fr;
/*
┌──────┬──────────┬──────────┐
│200px │ 1fr │ 1fr │
└──────┴──────────┴──────────┘
*/
/* repeat:3 等分 */
grid-template-columns: repeat(3, 1fr);
/*
┌────────┬────────┬────────┐
│ 1fr │ 1fr │ 1fr │
└────────┴────────┴────────┘
*/
/* repeat + auto-fill:自动填充,能放几个放几个 */
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
/*
容器 900px → 4 列各 225px
┌───────┬───────┬───────┬───────┐
│ 225 │ 225 │ 225 │ 225 │
└───────┴───────┴───────┴───────┘
容器 500px → 2 列各 250px
┌───────────┬───────────┐
│ 250 │ 250 │
└───────────┴───────────┘
*/
/* minmax:设定最小/最大范围 */
grid-template-rows: minmax(100px, auto);
/* 最少 100px,内容多了自动撑高 */
/* 指定行 + auto 隐式行 */
grid-template-rows: 80px 1fr 60px;
/* 第 1 行 80px,第 2 行撑满,第 3 行 60px */
/*
┌─────────────────────────┐
│ 80px(header) │
├─────────────────────────┤
│ │
│ 1fr(content 撑满) │
│ │
├─────────────────────────┤
│ 60px(footer) │
└─────────────────────────┘
*/gap — 行列间距
css
gap: 20px; /* 行列间距都是 20px */
/* 分开写 */
row-gap: 20px; /* 行间距 */
column-gap: 10px; /* 列间距 */
/*
┌──────┐ ← 20px → ┌──────┐
│ │ │ │
│ A │ │ B │
│ │ │ │
└──────┘ └──────┘
↕ 20px
┌──────┐ ┌──────┐
│ │ │ │
│ C │ │ D │
│ │ │ │
└──────┘ └──────┘
*/grid-template-areas — 可视化区域布局(最强功能)
css
.container {
display: grid;
grid-template-columns: 200px 1fr 1fr;
grid-template-rows: 60px 1fr 60px;
grid-template-areas:
"header header header"
"sidebar main main"
"footer footer footer";
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
/*
┌─────────────────────────────────────────┐
│ header │
├──────────┬──────────────────────────────┤
│ sidebar │ main │
│ 200px │ │
├──────────┴──────────────────────────────┤
│ footer │
└─────────────────────────────────────────┘
*/⚠️ 区域名不能是数字开头,区域必须形成矩形(不能是 L 形、T 形)。
justify-items / align-items — 单元格内容对齐
css
/* 默认 stretch:拉伸填满单元格 */
justify-items: stretch;
align-items: stretch;
/*
┌────────────┬────────────┐
│ ┌──────┐ │ ┌──────┐ │
│ │ 撑满 │ │ │ 撑满 │ │
│ │ 撑满 │ │ │ 撑满 │ │
│ └──────┘ │ └──────┘ │
└────────────┴────────────┘
*/
/* start:贴左上角 */
justify-items: start;
align-items: start;
/*
┌────────────┬────────────┐
│ ┌──┐ │ ┌──┐ │
│ │ │ │ │ │ │
│ └──┘ │ └──┘ │
│ │ │
│ │ │
└────────────┴────────────┘
*/
/* center:居中 */
justify-items: center;
align-items: center;
place-items: center; /* 简写 */
/*
┌────────────┬────────────┐
│ │ │
│ ┌──┐ │ ┌──┐ │
│ │ │ │ │ │ │
│ └──┘ │ └──┘ │
│ │ │
└────────────┴────────────┘
*/grid-auto-flow — 自动排列方向
css
/* row(默认):按行排列 */
grid-auto-flow: row;
/*
放入顺序:1→2→3→4→5→6
┌────┬────┬────┐
│ 1 │ 2 │ 3 │
├────┼────┼────┤
│ 4 │ 5 │ 6 │
└────┴────┴────┘
*/
/* column:按列排列 */
grid-auto-flow: column;
/*
┌────┬────┬────┐
│ 1 │ 3 │ 5 │
├────┼────┼────┤
│ 2 │ 4 │ 6 │
└────┴────┴────┘
*/
/* dense:自动填补空白 */
grid-auto-flow: dense;
/*
子项 1 跨 2 列,2、3 较小 → 自动填空
┌──────────┬────┬────┐
│ 1 │ 2 │ 3 │
├────┬─────┼────┴────┤
│ 4 │ 5 │ 6 │
└────┴─────┴─────────┘
*/子项属性
grid-column / grid-row — 基于网格线定位
网格线编号:
1 2 3 4
│ │ │ │
├─────────┼─────────┼─────────┤ ← 行 1
│ │ │ │
├─────────┼─────────┼─────────┤ ← 行 2
│ │ │ │
├─────────┼─────────┼─────────┤ ← 行 3
│ │ │ │
└─────────┴─────────┴─────────┘css
/* 占第 1 到第 3 列 */
grid-column: 1 / 3; /* 等价 grid-column-start:1; grid-column-end:3 */
/*
┌──────────────────────┬─────┐
│ │ │
│ 1 │ 2 │
│ │ │
├──────────┬───────────┼─────┤
│ 3 │ 4 │ 5 │
└──────────┴───────────┴─────┘
*/
/* 占 2 列(从第 2 条线开始) */
grid-column: 2 / span 2;
/*
┌─────┬──────────────────────┐
│ │ │
│ 1 │ 2 │
│ │ │
├─────┼──────────┬───────────┤
│ 3 │ 4 │ 5 │
└─────┴──────────┴───────────┘
*/
/* 占 2 行(从第 1 条线开始跨 2 行) */
grid-row: 1 / span 2;
/*
┌──────┬─────┬─────┐
│ │ │ │
│ 1 │ 2 │ 3 │
│ │ │ │
│ ├─────┼─────┤
│ │ 4 │ 5 │
└──────┴─────┴─────┘
*/Grid 经典布局速查
圣杯布局(grid-template-areas 版)
css
.grid {
display: grid;
grid-template-columns: 200px 1fr 200px;
grid-template-rows: 60px 1fr 60px;
min-height: 100vh;
grid-template-areas:
"header header header"
"left main right"
"footer footer footer";
}
/*
┌──────────────────────────────────────────┐
│ header │
├──────┬──────────────────────┬────────────┤
│ left │ main │ right │
│200px │ 1fr │ 200px │
├──────┴──────────────────────┴────────────┤
│ footer │
└──────────────────────────────────────────┘
*/瀑布流 / 自适应卡片
css
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
/*
容器 1100px 时 → 4 列
┌───────┬───────┬───────┬───────┐
│ card │ card │ card │ card │
├───────┼───────┼───────┼───────┤
│ card │ card │ card │ card │
└───────┴───────┴───────┴───────┘
容器 800px 时 → 3 列
┌──────────┬──────────┬──────────┐
│ card │ card │ card │
├──────────┼──────────┼──────────┤
│ card │ card │ card │
└──────────┴──────────┴──────────┘
*/不规则布局(不同子项占不同格数)
css
.grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.item1 { grid-column: span 2; grid-row: span 2; } /* 占 2×2 */
.item6 { grid-column: span 2; } /* 占 2×1 */
/*
┌──────────┬──────────┬─────┬─────┐
│ │ │ 2 │ 3 │
│ 1 │ 1 ├─────┼─────┤
│ │ │ 4 │ 5 │
├──────────┼──────────┼─────┼─────┤
│ 6 │ 6 │ 7 │ 8 │
└──────────┴──────────┴─────┴─────┘
*/Flex vs Grid:如何选择
| 场景 | 用 Flex | 用 Grid |
|---|---|---|
| 一维排列(导航、列表、工具栏) | ✅ | 也可以,但杀鸡用牛刀 |
| 二维布局(页面整体、仪表盘) | ❌ 需要嵌套 | ✅ |
| 内容驱动(子项大小决定布局) | ✅ flex: auto | ❌ 轨道是容器定义的 |
| 布局驱动(容器决定子项位置) | ❌ | ✅ grid-template-areas |
| 等分 n 列 | flex: 1 | repeat(n, 1fr) |
| 自适应卡片 | 需要 flex-wrap | auto-fill + minmax 更优雅 |
| 水平垂直居中 | ✅ 两行搞定 | place-items: center 一行搞定 |
| 不规则布局 | ❌ | ✅ grid-column/row: span |
面试话术:
Flex 是一维弹性布局,擅长在一条轴线上分配空间,适合组件级别的排列;Grid 是二维网格布局,擅长同时控制行和列,适合页面级别的整体布局。现代项目中通常 Grid 做页面骨架,Flex 做组件内部,两者配合使用。