主题
Flex 布局
Flex 布局模型概述
Flexbox(Flexible Box Layout)是 CSS3 引入的一维布局模型,专门用于处理容器内子元素的排列、对齐和空间分配。它的核心设计目标是:即使容器尺寸未知或动态变化,也能让子元素以可预测的方式排列。
传统布局方案(float + position + display: inline-block)本质上是对文档流的 hack,它们的设计初衷并不是做页面布局。Flexbox 是 W3C 专门为布局场景设计的规范,解决了传统方案中垂直居中困难、等高列困难、空间分配困难等核心痛点。
两根轴线
Flex 布局围绕两根轴线展开:
主轴方向(Main Axis)→ 由 flex-direction 决定
┌──────────────────────────────────────────────┐
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ Item │ │ Item │ │ Item │ │ Item │ │ ↕ 交叉轴方向
│ │ 1 │ │ 2 │ │ 3 │ │ 4 │ │ (Cross Axis)
│ └──────┘ └──────┘ └──────┘ └──────┘ │
└──────────────────────────────────────────────┘
Flex Container- 主轴(Main Axis):Flex 项目排列的方向,由
flex-direction属性决定 - 交叉轴(Cross Axis):与主轴垂直的方向
每根轴都有起点和终点:
main-start main-end
│ │
cross-start ── ┌────────────────────────────┐
│ [Item1] [Item2] [Item3] │
cross-end ──── └────────────────────────────┘理解这两根轴线是掌握 Flex 布局的基础——所有对齐属性(justify-*、align-*)都是相对于这两根轴而言的。
容器属性
在元素上设置 display: flex(或 display: inline-flex)即可将其变为 Flex 容器,其直接子元素自动成为 Flex 项目。
css
.container {
display: flex; /* 块级 flex 容器 */
/* display: inline-flex; */ /* 行内 flex 容器 */
}flex-direction
决定主轴的方向,即 Flex 项目的排列方向。
css
.container {
flex-direction: row; /* 默认值:水平方向,从左到右 */
flex-direction: row-reverse; /* 水平方向,从右到左 */
flex-direction: column; /* 垂直方向,从上到下 */
flex-direction: column-reverse; /* 垂直方向,从下到上 */
}row(默认):
→ [1] [2] [3]
row-reverse:
[3] [2] [1] ←
column: column-reverse:
↓ [3]
[1] [2]
[2] [1]
[3] ↑重要理解:flex-direction 改变的不只是视觉排列,而是改变了主轴的方向。这意味着 justify-content(主轴对齐)和 align-items(交叉轴对齐)的作用方向也随之改变。例如,当 flex-direction: column 时,justify-content 控制的是垂直方向的对齐,align-items 控制的是水平方向的对齐。
flex-wrap
控制 Flex 项目在主轴方向排满后是否换行。
css
.container {
flex-wrap: nowrap; /* 默认值:不换行,所有项目压缩在一行 */
flex-wrap: wrap; /* 换行,第一行在上方 */
flex-wrap: wrap-reverse; /* 换行,第一行在下方 */
}nowrap(默认):
[1] [2] [3] [4] [5] [6] ← 即使溢出也不换行,会压缩项目
wrap:
[1] [2] [3] [4]
[5] [6]
wrap-reverse:
[5] [6]
[1] [2] [3] [4]当 nowrap 时,如果项目总宽度超过容器宽度,每个项目会按 flex-shrink 的规则被压缩(默认 flex-shrink: 1)。这是很多初学者困惑"为什么我设置的 width 没生效"的根本原因。
flex-flow(简写)
flex-direction 和 flex-wrap 的简写属性:
css
.container {
flex-flow: row wrap; /* 水平排列,允许换行 */
flex-flow: column nowrap; /* 垂直排列,不换行 */
}justify-content
控制 Flex 项目在主轴上的对齐方式和空间分配。
css
.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:
|[1][2][3] |
flex-end:
| [1][2][3]|
center:
| [1][2][3] |
space-between:
|[1] [2] [3]|
space-around:
| [1] [2] [3] |
↑ ↑ ↑ ↑ ↑ ↑
a a 2a 2a a a (a = 单位间距)
space-evenly:
| [1] [2] [3] |
↑ ↑ ↑ ↑
a a a a (所有间距相等)space-around vs space-evenly 的区别:space-around 是每个项目两侧各分配等量空间,所以相邻项目之间的间距是两端间距的 2 倍;space-evenly 是所有间距(包括两端和项目之间)完全相等。
align-items
控制 Flex 项目在交叉轴上的对齐方式(单行情况)。
css
.container {
align-items: stretch; /* 默认值:拉伸填满容器交叉轴 */
align-items: flex-start; /* 向交叉轴起点对齐 */
align-items: flex-end; /* 向交叉轴终点对齐 */
align-items: center; /* 交叉轴居中 */
align-items: baseline; /* 按文字基线对齐 */
}stretch(默认): flex-start: center:
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│┌────┐┌────┐┌────┐│ │┌──┐┌────┐┌─┐ │ │ │
││ ││ ││ ││ │└──┘│ │└─┘ │ │┌──┐┌────┐┌─┐ │
││ 1 ││ 2 ││ 3 ││ │ │ 2 │ │ │└──┘│ 2 │└─┘ │
││ ││ ││ ││ │ └────┘ │ │ └────┘ │
│└────┘└────┘└────┘│ │ │ │ │
└──────────────────┘ └──────────────────┘ └──────────────────┘stretch 的生效条件:项目在交叉轴方向上不能有固定尺寸。如果设置了 height(当主轴水平时),stretch 不会生效。
baseline 对齐:当项目内文字大小不同时,baseline 对齐会让所有项目按第一行文字的基线对齐,而非按容器边缘对齐。这在导航栏等场景中非常有用。
baseline:
┌──────────────────────────┐
│ ┌──────┐ │
│ │ Big │ ┌──────┐ │
│ │ Text │ │Small │ ┌────┐ │
│ └──────┘ └──────┘ │Tiny│ │
│ ─────────────────────── ← baseline
│ └────┘ │
└──────────────────────────┘align-content
控制多行 Flex 项目在交叉轴上的对齐方式。只在 flex-wrap: wrap 且实际产生多行时生效。
css
.container {
flex-wrap: wrap;
align-content: stretch; /* 默认值:每行拉伸填满 */
align-content: flex-start; /* 多行整体向交叉轴起点对齐 */
align-content: flex-end; /* 多行整体向交叉轴终点对齐 */
align-content: center; /* 多行整体居中 */
align-content: space-between; /* 行与行之间等间距,首尾行贴边 */
align-content: space-around; /* 每行两侧等间距 */
}align-items vs align-content:align-items 是控制每个项目在其所在行内的交叉轴对齐;align-content 是控制多行作为一个整体在容器交叉轴方向上的对齐。只有单行时 align-content 无效。
gap
控制 Flex 项目之间的间距。这是比 margin 更优雅的间距方案——它只作用于项目之间,不会在容器边缘产生多余间距。
css
.container {
gap: 10px; /* 行间距和列间距都是 10px */
gap: 10px 20px; /* 行间距 10px,列间距 20px */
row-gap: 10px; /* 单独设置行间距 */
column-gap: 20px; /* 单独设置列间距 */
}项目属性
order
控制 Flex 项目的排列顺序。数值越小,排列越靠前。默认值为 0。
css
.item:nth-child(1) { order: 3; }
.item:nth-child(2) { order: 1; }
.item:nth-child(3) { order: 2; }DOM 顺序:[1] [2] [3]
视觉顺序:[2] [3] [1]注意:order 只改变视觉顺序,不改变 DOM 顺序和 Tab 键导航顺序,可能造成无障碍访问(Accessibility)问题。
flex-grow
定义项目的放大比例,即当容器有剩余空间时,项目如何分配剩余空间。默认值为 0(不放大)。
css
.item { flex-grow: 0; } /* 默认值:不放大 */
.item { flex-grow: 1; } /* 等比例放大 */计算逻辑:
容器宽度:500px
Item1: width=100px, flex-grow=1
Item2: width=100px, flex-grow=2
Item3: width=100px, flex-grow=1
剩余空间 = 500 - (100 + 100 + 100) = 200px
flex-grow 总和 = 1 + 2 + 1 = 4
Item1 最终宽度 = 100 + 200 × (1/4) = 150px
Item2 最终宽度 = 100 + 200 × (2/4) = 200px
Item3 最终宽度 = 100 + 200 × (1/4) = 150px
验证:150 + 200 + 150 = 500 ✓flex-shrink
定义项目的缩小比例,即当容器空间不足时,项目如何缩小。默认值为 1(等比例缩小)。
css
.item { flex-shrink: 1; } /* 默认值:可缩小 */
.item { flex-shrink: 0; } /* 不缩小 */计算逻辑(比 flex-grow 复杂——缩小时需要考虑项目本身的大小权重):
容器宽度:400px
Item1: width=200px, flex-shrink=1
Item2: width=200px, flex-shrink=2
Item3: width=200px, flex-shrink=1
溢出空间 = (200 + 200 + 200) - 400 = 200px
加权缩小因子:
Item1: 200 × 1 = 200
Item2: 200 × 2 = 400
Item3: 200 × 1 = 200
总和 = 800
Item1 缩小量 = 200 × (200/800) = 50px → 最终 150px
Item2 缩小量 = 200 × (400/800) = 100px → 最终 100px
Item3 缩小量 = 200 × (200/800) = 50px → 最终 150px
验证:150 + 100 + 150 = 400 ✓为什么 flex-shrink 的计算要乘以项目宽度? 因为绝对等比例缩小是不合理的——一个 100px 的项目和一个 500px 的项目如果缩小相同的量,对 100px 项目的视觉影响远大于 500px 项目。乘以项目宽度作为权重,确保缩小的"痛感"是相对公平的。
flex-basis
定义在分配剩余空间之前,项目占据的主轴空间。浏览器根据这个属性计算主轴是否有多余空间。
css
.item { flex-basis: auto; } /* 默认值:使用项目本身的 width/height */
.item { flex-basis: 0; } /* 完全忽略项目本身尺寸,空间完全由 flex-grow 分配 */
.item { flex-basis: 200px; } /* 固定基准宽度 */
.item { flex-basis: 30%; } /* 百分比基准宽度 */flex-basis vs width 的优先级:
flex-basis(非 auto)> width > 内容宽度
当 flex-basis: auto 时:
width > 内容宽度
即:flex-basis 设置了非 auto 值后,width 会被忽略flex-basis: 0 vs flex-basis: auto 的关键区别:
容器宽度:600px
Item1: content="Hi", flex-grow=1
Item2: content="Hello World", flex-grow=1
flex-basis: auto 时:
Item1 基准 = "Hi" 的宽度 ≈ 20px
Item2 基准 = "Hello World" 的宽度 ≈ 100px
剩余空间 = 600 - 120 = 480px → 各分 240px
Item1 最终 = 260px, Item2 最终 = 340px(不等宽)
flex-basis: 0 时:
Item1 基准 = 0
Item2 基准 = 0
剩余空间 = 600 - 0 = 600px → 各分 300px
Item1 最终 = 300px, Item2 最终 = 300px(等宽)flex(简写属性)
flex 是 flex-grow、flex-shrink、flex-basis 的简写,这也是实际开发中最常用的写法。
css
.item { flex: none; } /* 0 0 auto — 不放大不缩小 */
.item { flex: auto; } /* 1 1 auto — 可放大可缩小,基于内容宽度 */
.item { flex: 1; } /* 1 1 0% — 等分空间(最常用) */
.item { flex: 2; } /* 2 1 0% — 占 2 份空间 */
.item { flex: 0 0 200px; } /* 固定 200px,不伸缩 */
.item { flex: 1 0 200px; } /* 最小 200px,可放大 */flex: 1 的完整展开:
css
flex: 1;
/* 等价于 */
flex-grow: 1;
flex-shrink: 1;
flex-basis: 0%;为什么推荐用 flex 简写而不是分开写? 因为 flex 简写有智能默认值。单独写 flex-grow: 1 时,flex-basis 默认是 auto;而写 flex: 1 时,flex-basis 默认是 0%。这两者的空间分配逻辑完全不同(见上文 flex-basis 的对比分析)。
align-self
允许单个项目覆盖容器的 align-items 设置。
css
.item {
align-self: auto; /* 默认值:继承容器的 align-items */
align-self: flex-start;
align-self: flex-end;
align-self: center;
align-self: baseline;
align-self: stretch;
}容器 align-items: flex-start
┌─────────────────────────────┐
│ ┌──┐ ┌──┐ ┌──┐ │
│ │1 │ │2 │ │4 │ │
│ └──┘ └──┘ ┌──┐ └──┘ │
│ │3 │ │
│ │ │ │
│ └──┘ │
│ align-self: flex-end │
└─────────────────────────────┘flex-grow / flex-shrink / flex-basis 协作机制
这三个属性共同决定了 Flex 项目的最终尺寸。理解它们的协作流程是掌握 Flex 布局的核心。
Flex 尺寸计算的完整流程
步骤1: 确定 flex-basis
↓
步骤2: 计算剩余空间或溢出空间
剩余空间 = 容器主轴尺寸 - Σ(各项目的 flex-basis)
↓
步骤3: 根据正负决定使用 grow 还是 shrink
if 剩余空间 > 0 → 按 flex-grow 分配剩余空间
if 剩余空间 < 0 → 按 flex-shrink × flex-basis 加权缩小
if 剩余空间 = 0 → 各项目保持 flex-basis 大小
↓
步骤4: 应用 min-width / max-width 约束
如果计算结果超出 min/max 限制,固定该项目尺寸,
将剩余空间重新分配给其他项目(进入新一轮计算)
↓
步骤5: 得到最终尺寸综合示例
css
.container {
display: flex;
width: 800px;
}
.item-a { flex: 2 1 200px; max-width: 350px; }
.item-b { flex: 1 1 100px; }
.item-c { flex: 1 2 200px; min-width: 150px; }步骤1: flex-basis → A=200, B=100, C=200
步骤2: 剩余空间 = 800 - 500 = 300px(正数,使用 flex-grow)
步骤3: grow 总和 = 2+1+1 = 4
A 增加 = 300 × 2/4 = 150px → 350px
B 增加 = 300 × 1/4 = 75px → 175px
C 增加 = 300 × 1/4 = 75px → 275px
步骤4: A 的 max-width 为 350px → 刚好等于 350px ✓
C 的 min-width 为 150px → 275px > 150px ✓
步骤5: 最终 A=350px, B=175px, C=275px
验证: 350 + 175 + 275 = 800 ✓常见布局实战
水平垂直居中
Flex 布局实现居中是最简洁优雅的方案:
css
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}┌──────────────────────────────┐
│ │
│ │
│ ┌──────────┐ │
│ │ Centered │ │
│ └──────────┘ │
│ │
│ │
└──────────────────────────────┘经典三栏布局(圣杯布局)
左右固定宽度,中间自适应:
css
.container {
display: flex;
}
.left {
flex: 0 0 200px;
}
.center {
flex: 1;
}
.right {
flex: 0 0 150px;
}┌────────┬──────────────────────┬──────┐
│ │ │ │
│ Left │ Center │ Right│
│ 200px │ (自适应) │150px │
│ │ │ │
└────────┴──────────────────────┴──────┘flex: 0 0 200px 表示不放大、不缩小、固定 200px。flex: 1 表示占满剩余空间。
等高列布局
传统 CSS 实现等高列非常困难,Flex 布局天然支持:
css
.container {
display: flex;
/* align-items 默认值就是 stretch */
}
.column {
flex: 1;
padding: 20px;
}┌──────────────┬──────────────┬──────────────┐
│ Column 1 │ Column 2 │ Column 3 │
│ │ │ │
│ Short │ This column │ Medium │
│ content │ has much │ length │
│ │ more content │ content │
│ │ than the │ │
│ │ others │ │
│ │ │ │ ← 所有列高度相同
└──────────────┴──────────────┴──────────────┘align-items: stretch(默认值)会让所有项目在交叉轴上拉伸到相同高度。
Sticky Footer(底部粘连)
页面内容不足一屏时 Footer 始终贴底,内容超出时正常文档流:
css
.page {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.header {
flex-shrink: 0;
}
.main {
flex: 1;
}
.footer {
flex-shrink: 0;
}内容不足一屏时: 内容超出一屏时:
┌──────────────┐ ┌──────────────┐
│ Header │ │ Header │
├──────────────┤ ├──────────────┤
│ │ │ │
│ Main │ │ Main │
│ (flex: 1) │ │ (很多内容) │
│ │ │ │
│ │ │ │
├──────────────┤ │ │
│ Footer │ ├──────────────┤
└──────────────┘ │ Footer │
└──────────────┘自适应导航栏
左侧 logo,右侧导航项,中间自动填充:
css
.navbar {
display: flex;
align-items: center;
}
.logo {
flex-shrink: 0;
}
.spacer {
flex: 1;
}
.nav-items {
display: flex;
gap: 16px;
}或者更优雅的方式,使用 margin-left: auto:
css
.navbar {
display: flex;
align-items: center;
}
.nav-items {
margin-left: auto;
}┌────────────────────────────────────┐
│ [Logo] [Nav1] [Nav2] │
└────────────────────────────────────┘
↑ margin-left: auto原理:Flex 布局中,margin: auto 会吞掉该方向上的所有剩余空间。这是 Flex 规范中的特殊行为,不同于块级元素中的 margin: auto。
响应式卡片网格
使用 flex-wrap 实现自动换行的卡片布局:
css
.grid {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
.card {
flex: 1 1 300px;
max-width: calc(33.333% - 12px);
}宽屏幕(≥ 960px):
┌──────┐ ┌──────┐ ┌──────┐
│ Card │ │ Card │ │ Card │
└──────┘ └──────┘ └──────┘
中等屏幕(640px ~ 959px):
┌──────────┐ ┌──────────┐
│ Card │ │ Card │
└──────────┘ └──────────┘
┌──────────┐
│ Card │
└──────────┘
小屏幕(< 640px):
┌────────────────────┐
│ Card │
└────────────────────┘
┌────────────────────┐
│ Card │
└────────────────────┘flex: 1 1 300px 的含义:基准宽度 300px,可放大可缩小。当容器宽度不足以放下两个 300px 项目时,自动换行。
Flex 布局中的特殊行为
margin: auto 的吞噬效应
在 Flex 布局中,margin: auto 的行为与块级元素不同。它会在对应方向上吞掉所有剩余空间,优先级高于 justify-content 和 align-items。
css
.container {
display: flex;
justify-content: flex-start;
}
.item:last-child {
margin-left: auto;
}即使 justify-content: flex-start,最后一个元素也会被推到最右边。因为 margin: auto 在 justify-content 之前生效——当任何 Flex 项目有 auto 的 margin 时,justify-content 不会产生效果。
min-width: auto 的陷阱
Flex 项目的 min-width 默认值不是 0,而是 auto。这意味着 Flex 项目不会缩小到比其内容更小的尺寸。
css
.container {
display: flex;
width: 300px;
}
.item {
flex: 1;
}如果 .item 内部有一段不换行的长文本或一张大图片,它的宽度可能会超过 300px / n,因为 min-width: auto 阻止了项目缩小到内容尺寸以下。
解决方案:
css
.item {
flex: 1;
min-width: 0; /* 允许缩小到 0 */
/* 或 */
overflow: hidden; /* 也能解决,因为 overflow 非 visible 会改变 min-width 的计算 */
}文本溢出处理
在 Flex 项目中实现文本省略号需要同时解决 min-width 问题:
css
.item {
flex: 1;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}如果不设置 min-width: 0,text-overflow: ellipsis 可能不会生效,因为项目不会被压缩到文本宽度以下。
Flex 布局 vs Grid 布局
| 维度 | Flex | Grid |
|---|---|---|
| 维度 | 一维(行或列) | 二维(行和列同时) |
| 适用场景 | 线性排列的组件(导航、工具栏、卡片列表) | 复杂的二维页面布局(整体页面结构、仪表盘) |
| 内容驱动 vs 布局驱动 | 内容驱动:项目大小影响布局 | 布局驱动:先定义网格再放置项目 |
| 对齐能力 | 主轴 + 交叉轴 | 行轴 + 列轴,且支持 grid-area 命名布局 |
| 项目排列 | 沿一个方向流式排列 | 可以精确放置到网格的任意位置 |
| 间距 | gap | gap(与 Flex 相同) |
| 换行后的对齐 | align-content 控制多行对齐 | 天然支持,网格行/列独立控制 |
选择策略:
- 需要让内容决定布局→ Flex(如导航栏项目数量不确定)
- 需要让布局决定内容的放置→ Grid(如仪表盘固定的网格区域)
- 一维排列→ Flex
- 二维布局→ Grid
- 两者可以嵌套使用——外层 Grid 定义页面结构,内层 Flex 处理组件内部排列
经典面试题解析
题目一:flex: 1 到底意味着什么?
flex: 1 是 flex: 1 1 0% 的简写:
flex-grow: 1— 有剩余空间时按比例放大flex-shrink: 1— 空间不足时按比例缩小flex-basis: 0%— 在分配前不占据空间,完全由 flex-grow 分配
与 flex: auto(1 1 auto)的区别是 flex-basis:auto 会先按内容/width 计算基准大小,剩余空间再按 grow 分配;0% 则完全忽略内容大小,所有空间都由 grow 按比例分配。
题目二:如何实现一个不定宽高的元素水平垂直居中?
方案一:Flex 居中
css
.parent {
display: flex;
justify-content: center;
align-items: center;
}方案二:Flex + margin
css
.parent {
display: flex;
}
.child {
margin: auto;
}方案三:Grid 居中
css
.parent {
display: grid;
place-items: center;
}方案四:绝对定位 + transform
css
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}题目三:flex-shrink 的计算为什么要乘以 flex-basis?
因为缩小是一个相对操作。假设两个项目 A(基准 100px)和 B(基准 500px),都设置 flex-shrink: 1,需要总共缩小 120px:
如果简单等比缩小(不乘以 basis):A 缩 60px → 40px,B 缩 60px → 440px。A 缩小了 60%,B 只缩小了 12%。这对小元素很不公平。
乘以 basis 加权缩小:A 加权 = 100×1 = 100,B 加权 = 500×1 = 500,总 = 600。A 缩 120×(100/600) = 20px → 80px,B 缩 120×(500/600) = 100px → 400px。A 缩小 20%,B 缩小 20%。公平合理。
题目四:为什么设置了 flex-basis,width 没有生效?
当 flex-basis 的值不是 auto 时,它会覆盖 width(或 height,取决于主轴方向)。这是 Flex 规范的设计——flex-basis 是 Flex 布局专用的"初始主轴尺寸"属性,优先级高于通用的 width/height。
优先级链:max-width/min-width > flex-basis(非 auto)> width > 内容尺寸
题目五:Flex 项目中文本为什么不会被省略号截断?
css
.container { display: flex; width: 200px; }
.item {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}以上代码中省略号可能不生效,原因是 Flex 项目的 min-width 默认值是 auto,它不允许项目缩小到内容宽度以下。解决方案是添加 min-width: 0 或 overflow: hidden。
追问思考
display: flex和display: inline-flex在布局上有什么区别?对子元素有影响吗?- 当
flex-wrap: wrap时,align-items和align-content分别控制什么?如果只有一行,哪个生效? flex-basis: 0和flex-basis: 0%有区别吗?(提示:考虑容器尺寸未确定的情况)- 为什么 Flex 布局中
margin: auto能实现居中,而块级元素的margin: auto只能水平居中? - 一个 Flex 容器中所有项目都设置
flex-shrink: 0,当内容溢出时会发生什么?