Flex 布局
问题
Flex 布局的核心概念是什么?容器属性和项目属性分别有哪些?如何用 Flex 实现常见布局?
答案
Flex 布局概述
Flex(Flexible Box,弹性盒子)是 CSS3 中专门为一维布局设计的模型。它可以轻松实现元素的对齐、排列、分配空间,极大简化了传统浮动和定位布局的复杂性。
.container {
display: flex; /* 块级 Flex 容器 */
/* 或 */
display: inline-flex; /* 行内 Flex 容器 */
}
- Flex:一维布局(一行或一列),适合组件内部的排列
- Grid:二维布局(行和列同时控制),适合整体页面布局
详见 Grid 布局。
核心概念
主轴 (Main Axis) →
┌──────────────────────────────────────┐
│ ┌─────┐ ┌─────┐ ┌─────┐ │ ↕ 交叉轴
│ │ 1 │ │ 2 │ │ 3 │ │ (Cross Axis)
│ └─────┘ └─────┘ └─────┘ │
└──────────────────────────────────────┘
Flex Container(容器)
└── Flex Items(项目)
| 概念 | 说明 |
|---|---|
| Flex Container | 设置了 display: flex 的父元素 |
| Flex Item | 容器的直接子元素 |
| 主轴(Main Axis) | Flex 项目的排列方向,默认水平 → |
| 交叉轴(Cross Axis) | 与主轴垂直的方向,默认垂直 ↓ |
| main-start / main-end | 主轴的起点和终点 |
| cross-start / cross-end | 交叉轴的起点和终点 |
容器属性(Container Properties)
1. flex-direction — 主轴方向
.container {
flex-direction: row; /* 默认:水平从左到右 → */
flex-direction: row-reverse; /* 水平从右到左 ← */
flex-direction: column; /* 垂直从上到下 ↓ */
flex-direction: column-reverse; /* 垂直从下到上 ↑ */
}
2. flex-wrap — 换行方式
.container {
flex-wrap: nowrap; /* 默认:不换行(项目可能压缩) */
flex-wrap: wrap; /* 换行,从上到下排列 */
flex-wrap: wrap-reverse; /* 换行,从下到上排列 */
}
3. flex-flow — 简写
.container {
flex-flow: row wrap; /* = flex-direction: row + flex-wrap: wrap */
}
4. justify-content — 主轴对齐
.container {
justify-content: flex-start; /* 默认:靠起点对齐 */
justify-content: flex-end; /* 靠终点对齐 */
justify-content: center; /* 居中 */
justify-content: space-between; /* 两端对齐,项目间等间距 */
justify-content: space-around; /* 每个项目两侧间距相等 */
justify-content: space-evenly; /* 所有间距完全相等 */
}
flex-start: |■ ■ ■ |
flex-end: | ■ ■ ■|
center: | ■ ■ ■ |
space-between: |■ ■ ■|
space-around: | ■ ■ ■ |
space-evenly: | ■ ■ ■ |
space-between:首尾项目贴边,中间等间距。间距 = 剩余空间 / (n-1)space-around:每个项目两侧间距相等 → 首尾项目到边缘的间距是中间间距的 一半space-evenly:所有间距(含到边缘)完全相等
5. align-items — 交叉轴对齐(单行)
.container {
align-items: stretch; /* 默认:拉伸填满容器高度 */
align-items: flex-start; /* 靠交叉轴起点 */
align-items: flex-end; /* 靠交叉轴终点 */
align-items: center; /* 居中 */
align-items: baseline; /* 按文字基线对齐 */
}
6. align-content — 交叉轴对齐(多行)
当有多行时(flex-wrap: wrap),align-content 控制行与行之间的间距,取值与 justify-content 类似。
.container {
flex-wrap: wrap;
align-content: flex-start; /* 靠顶部 */
align-content: flex-end; /* 靠底部 */
align-content: center; /* 居中 */
align-content: space-between; /* 首尾行贴边 */
align-content: space-around; /* 均匀分布 */
align-content: stretch; /* 默认:行拉伸填满 */
}
align-items:控制单行内项目在交叉轴上的对齐align-content:控制多行之间在交叉轴上的分布- 只有一行时
align-content无效
7. gap — 间距
.container {
gap: 16px; /* 行间距和列间距都是 16px */
gap: 16px 24px; /* 行间距 16px,列间距 24px */
row-gap: 16px; /* 仅行间距 */
column-gap: 24px; /* 仅列间距 */
}
gap 只作用于项目之间,不影响首尾项目到容器的距离。比起每个项目设 margin 再处理首尾特殊情况,gap 更简洁。
项目属性(Item Properties)
1. flex-grow — 放大比例
定义项目的放大比例,分配剩余空间。默认值 0(不放大)。
.item-a { flex-grow: 1; } /* 占 1 份 */
.item-b { flex-grow: 2; } /* 占 2 份 */
.item-c { flex-grow: 1; } /* 占 1 份 */
/* A 和 C 各占剩余空间的 1/4,B 占 2/4 */
2. flex-shrink — 缩小比例
定义项目的缩小比例,当空间不足时如何分配压缩量。默认值 1(等比缩小)。
.item-a { flex-shrink: 0; } /* 不缩小 */
.item-b { flex-shrink: 1; } /* 默认:参与缩小 */
.item-c { flex-shrink: 2; } /* 缩小量是 B 的 2 倍 */
缩小量的分配与 flex-shrink 值和项目自身宽度的乘积成正比,而非简单的比例关系。
假设容器宽 300px,A(150px, shrink:1)、B(200px, shrink:2),溢出 50px:
- A 的权重 = 150 × 1 = 150
- B 的权重 = 200 × 2 = 400
- A 缩小 = 50 × 150/(150+400) ≈ 13.6px
- B 缩小 = 50 × 400/(150+400) ≈ 36.4px
3. flex-basis — 初始尺寸
定义项目在分配剩余空间之前的基础尺寸。默认值 auto(即项目自身的 width/height)。
.item {
flex-basis: 200px; /* 初始宽度 200px */
flex-basis: 30%; /* 初始宽度为容器的 30% */
flex-basis: auto; /* 默认:使用 width 属性 */
flex-basis: 0; /* 不考虑自身尺寸,完全由 flex-grow 分配 */
}
当 flex-basis 和 width 同时设置时,flex-basis 优先级更高(flex-direction: column 时对应 height)。但 flex-basis 受 min-width 和 max-width 约束。
4. flex — 简写(重要)
.item {
flex: <grow> <shrink> <basis>;
}
常用简写值:
| 写法 | 等价于 | 说明 |
|---|---|---|
flex: 1 | flex: 1 1 0% | 等分剩余空间 |
flex: auto | flex: 1 1 auto | 基于内容大小弹性伸缩 |
flex: none | flex: 0 0 auto | 不伸不缩,固定大小 |
flex: 0 | flex: 0 1 0% | 不放大,可缩小 |
flex: 2 | flex: 2 1 0% | 占 2 份空间 |
这是高频面试题!关键区别在于 flex-basis:
flex: 1→flex-basis: 0%→ 不考虑内容大小,完全按比例分配flex: auto→flex-basis: auto→ 先按内容大小分配,剩余空间再按比例分配
/* 三个项目,内容长度不同 */
.equal > * { flex: 1; } /* 三个项目宽度完全相等 */
.auto > * { flex: auto; } /* 内容多的项目更宽 */
5. align-self — 单独对齐
覆盖容器的 align-items,让某个项目单独在交叉轴上对齐。
.container { align-items: flex-start; }
.special-item {
align-self: center; /* 只有这个项目居中 */
}
6. order — 排列顺序
.item-a { order: 3; } /* 排第三 */
.item-b { order: 1; } /* 排第一 */
.item-c { order: 2; } /* 排第二 */
/* 默认 order: 0,值越小越靠前 */
常见布局实现
导航栏(Logo 左 + 菜单右)
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
height: 64px;
}
等分布局
.grid {
display: flex;
gap: 16px;
}
.grid > .col {
flex: 1; /* 等分空间 */
}
固定侧边栏 + 自适应内容
.layout {
display: flex;
height: 100vh;
}
.sidebar {
flex: none; /* 不伸缩 */
width: 240px;
}
.main {
flex: 1; /* 占满剩余空间 */
overflow: auto;
}
粘性底部(Sticky Footer)
.page {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.header { /* 固定高度 */ }
.content {
flex: 1; /* 占满中间空间 */
}
.footer { /* 固定高度,始终在底部 */ }
水平垂直居中
.container {
display: flex;
justify-content: center;
align-items: center;
}
更多居中方案详见 CSS 居中方案。
常见面试问题
Q1: Flex 容器的属性有哪些?
答案:
6 个容器属性 + 1 个间距属性:
| 属性 | 作用 | 默认值 |
|---|---|---|
flex-direction | 主轴方向 | row |
flex-wrap | 是否换行 | nowrap |
flex-flow | direction + wrap 简写 | row nowrap |
justify-content | 主轴对齐方式 | flex-start |
align-items | 交叉轴对齐(单行) | stretch |
align-content | 交叉轴对齐(多行) | stretch |
gap | 项目间距 | 0 |
Q2: flex: 1 是什么含义?和 flex: auto 有什么区别?
答案:
| 写法 | 展开 | flex-basis | 行为 |
|---|---|---|---|
flex: 1 | 1 1 0% | 0% | 忽略内容大小,完全按比例分配 |
flex: auto | 1 1 auto | auto | 先考虑内容大小,剩余空间按比例分配 |
<div style="display: flex; width: 300px;">
<div style="flex: 1;">短</div>
<div style="flex: 1;">很长很长的文本内容</div>
</div>
<!-- flex: 1 → 两个 div 各 150px -->
<div style="display: flex; width: 300px;">
<div style="flex: auto;">短</div>
<div style="flex: auto;">很长很长的文本内容</div>
</div>
<!-- flex: auto → 长文本的 div 更宽 -->
Q3: flex-shrink 的缩小比例怎么计算?
答案:
缩小量与 flex-shrink × flex-basis 的乘积成正比。
溢出量 = 所有项目总宽度 - 容器宽度
项目缩小量 = 溢出量 × (该项目的 shrink × basis) / (所有项目的 shrink × basis 之和)
例如容器 400px,三个项目 A(200px, shrink:1)、B(200px, shrink:2)、C(200px, shrink:1):
- 溢出 = 600 - 400 = 200px
- A 权重 = 200×1 = 200,B 权重 = 200×2 = 400,C 权重 = 200×1 = 200
- A 缩小 = 200 × 200/800 = 50px → 最终 150px
- B 缩小 = 200 × 400/800 = 100px → 最终 100px
- C 缩小 = 200 × 200/800 = 50px → 最终 150px
Q4: align-items 和 align-content 的区别?
答案:
| 特性 | align-items | align-content |
|---|---|---|
| 作用对象 | 每一行内的项目 | 多行之间 |
| 生效条件 | 始终生效 | 仅在 flex-wrap: wrap 且有多行时 |
| 可选值 | stretch/start/end/center/baseline | stretch/start/end/center/space-between/space-around |
单行时只需关心 align-items,多行时两者配合使用。
Q5: flex-basis 和 width 哪个优先级更高?
答案:
当同时设置时,flex-basis 优先级更高。但都受 min-width / max-width 约束:
max-width / min-width > flex-basis > width
.item {
width: 200px;
flex-basis: 300px;
max-width: 250px;
}
/* 最终基础宽度 = min(300px, 250px) = 250px */
当 flex-basis: auto 时,会使用 width 的值作为基础尺寸。
Q6: 如何让 Flex 项目换行后每行保持等分?
答案:
.container {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
.item {
/* 每行 3 个,减去 gap */
flex: 0 0 calc((100% - 16px * 2) / 3);
/* 或使用 width */
width: calc((100% - 16px * 2) / 3);
}
但对于网格布局场景,更推荐使用 Grid 布局。
Q7: Flex 布局中文本溢出省略号为什么失效了?
答案:
Flex 项目的默认 min-width 是 auto(即内容的最小宽度),导致项目不会被压缩到内容以下。解决方案:
.flex-item {
min-width: 0; /* 或 overflow: hidden */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
嵌套 Flex 时,每一层都需要设置 min-width: 0 或 overflow: hidden。
Q8: order 属性对可访问性有什么影响?
答案:
order 只改变视觉顺序,不改变 DOM 顺序。这意味着:
- Tab 键仍按 DOM 顺序聚焦
- 屏幕阅读器仍按 DOM 顺序读取
如果视觉顺序和 DOM 顺序严重不一致,会导致键盘用户困惑。建议尽量通过调整 HTML 结构来控制顺序,而非过度依赖 order。
Q9: flex-direction: column 时,主轴和交叉轴怎么变?
答案:
主轴变为垂直方向,交叉轴变为水平方向:
| 属性 | row(默认) | column |
|---|---|---|
| 主轴方向 | 水平 → | 垂直 ↓ |
| 交叉轴方向 | 垂直 ↓ | 水平 → |
justify-content 控制 | 水平对齐 | 垂直对齐 |
align-items 控制 | 垂直对齐 | 水平对齐 |
flex-basis 影响 | 宽度 | 高度 |
Q10: 如何用 Flex 实现圣杯布局?
答案:
.layout {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.header, .footer {
flex: none; /* 固定高度 */
}
.body {
display: flex;
flex: 1;
}
.left-sidebar {
flex: none;
width: 200px;
order: -1; /* DOM 中 main 在前,视觉上 sidebar 在左 */
}
.main {
flex: 1;
}
.right-sidebar {
flex: none;
width: 200px;
}