初步认识
为什么会出现 Flex?(问题的起点)
旧时代的布局困境
在 Flex 出现之前,开发者依赖以下方式进行布局:
| 布局方式 | 核心思路 | 问题 |
|---|---|---|
| 普通文档流 | 块级元素垂直排列,行内元素水平排列 | 只能单列排版,难以实现横向居中或等高布局 |
| 浮动布局 | 利用 float 让元素并排 | 本意是“文字环绕”,而不是布局工具;需手动清除浮动 |
| 定位布局 | absolute/fixed 精确放置位置 | 脱离文档流,无法自适应;难以响应变化 |
| inline-block 布局 | 利用行内特性并排 | 受空白节点干扰,垂直对齐难以控制 |
Flex 出现的目的
W3C 在 2012 年提出 Flexible Box Layout 模块,
为了解决:
“在一个维度(行或列)上,如何让盒子自然地、可伸缩地分布并对齐”。
也就是说:
Flex 是在“不破坏文档流整体逻辑的前提下”,引入“弹性空间分配机制”的排版模型。
Flex 是什么?(核心概念)
Flex 本质是:
一种一维布局模型,用于在一个主轴(main axis)方向上自动分配空间。
换句话说:
Flex 布局不是替代 BFC,而是在 BFC 内部引入新的一种排版规则(Flex Formatting Context,FFC)。
当一个元素设置:
.parent {
display: flex;
}
浏览器会让这个元素创建一个新的 Flex Formatting Context(FFC),
就像 BFC 一样,它会影响其子元素的排列逻辑。
Flex 的内部结构(像树一样的流动)
让我们从“文档流的树结构”视角看 Flex:
文档流(Document Flow)
└── BFC(块级格式化上下文)
└── Flex Container(display: flex)
├── 主轴:横向或纵向流动(main axis)
└── 交叉轴:垂直于主轴方向(cross axis)
├── Flex Item 1
├── Flex Item 2
└── Flex Item 3
形象理解:
- 普通 BFC:子盒子“堆叠”;
- Flex FFC:子盒子“排成一行(或一列)”;
- 子项之间的间距、伸缩、对齐,都交给“弹性规则”自动分配。
Flex 如何在排版阶段起作用(底层逻辑)
在浏览器渲染过程中:
- 创建 FFC:当
display:flex出现时,容器生成新的 Flex Formatting Context; - 确定主轴方向:
flex-direction: row/column; - 测量主轴尺寸:根据每个子项的初始宽高(或内容大小);
- 分配剩余空间:根据
flex-grow/flex-shrink/flex-basis; - 对齐(alignment):主轴用
justify-content,交叉轴用align-items。
示例:
<div class="parent">
<div class="child">A</div>
<div class="child">B</div>
<div class="child">C</div>
</div>
<style>
.parent {
display: flex;
justify-content: space-around; /* 主轴分布 */
align-items: center; /* 交叉轴对齐 */
height: 200px;
background: #eee;
}
.child {
flex: 1; /* 均分剩余空间 */
margin: 0 10px;
background: #8bc;
}
</style>
Flex 与其他上下文的关系(纵向维度认知)
| 维度 | 名称 | 特征 | Flex 的位置 |
|---|---|---|---|
| 文档流(宏观) | Document Flow | 所有盒子的“存在秩序” | Flex 依然属于文档流 |
| 格式化上下文 | BFC / IFC / FFC | 各种排版规则 | Flex 是新的 FFC |
| 层叠上下文 | z-index 控制层级 | 控制重叠顺序 | Flex 不主动创建层叠上下文 |
| 合成层 | GPU 绘制层 | 控制渲染性能 | 与布局逻辑无直接关系 |
结论:
Flex 不改变文档流的大框架,只是在原有 BFC 中,插入了一个“更聪明的内部排版系统(FFC)”。
举例:浮动与 Flex 的区别(形象类比)
| 特性 | 浮动 | Flex |
|---|---|---|
| 初衷 | 文字环绕 | 弹性布局 |
| 是否脱离文档流 | 部分脱离 | 不脱离 |
| 对齐方式 | 依赖 float、clear | 依赖 justify-content、align-items |
| 等高布局 | 需要计算 | 天然支持 |
| 子项空间分配 | 静态 | 动态(flex-grow/shrink) |
形象类比:
浮动像是在水上放木板(得手动摆好),
Flex 像是伸缩的橡皮筋,一根轴上自动调整各段长度。
总结(从文档流视角看 Flex)
| 层次 | 传统机制 | Flex 改进点 |
|---|---|---|
| 文档流结构 | 垂直流(BFC)+ 行内流(IFC) | 新增一维弹性流(FFC) |
| 排版逻辑 | 内容决定盒子 | 空间与比例共同决定盒子 |
| 对齐机制 | margin/padding 手动控制 | justify / align 自动控制 |
| 核心理念 | 静态排版 | 弹性分配 |
Flex 主轴的计算机制(flex-grow、flex-shrink、flex-basis 如何计算、空间分配算法)
Flex 的诞生目的:从「固定」到「弹性」
在传统 BFC 布局中,空间分配是“刚性”的:
- 每个块的宽度由内容或父容器约束决定;
- 如果空间多了,就留白;
- 空间少了,就挤爆或换行。
而 Flex 的核心思想就是:
“让子盒子根据剩余空间自动伸缩。”
这意味着:
Flex 的布局过程,是一个 “测量 + 分配 + 收敛” 的动态过程。
三个核心属性的本质作用
Flex 子项的伸缩能力,由以下三个属性控制:
| 属性 | 含义 | 类比 |
|---|---|---|
| flex-basis | 元素的“理想基准宽度” | 初始体积 |
| flex-grow | 容器空间多时的“伸展系数” | 肌肉能膨胀多少 |
| flex-shrink | 容器空间少时的“收缩系数” | 被压扁的弹性 |
这三个值共同决定了“弹性分配规则”。
Flex 的计算过程(主轴算法全景)
当一个容器被设为 display:flex; 时,浏览器会执行以下算法流程:
Step 1. 确定主轴方向(main axis)
Step 2. 确定每个 flex item 的基准尺寸(flex-basis)
Step 3. 计算剩余空间(free space)
Step 4. 分配伸缩量(grow/shrink)
Step 5. 应用对齐规则(justify-content / align-items)
让我们具体看每一步 👇
Step 1:确定主轴方向
通过 flex-direction 决定:
| 值 | 主轴方向 | 交叉轴方向 |
|---|---|---|
| row | 水平(左→右) | 垂直(上→下) |
| row-reverse | 水平(右→左) | 垂直(上→下) |
| column | 垂直(上→下) | 水平(左→右) |
| column-reverse | 垂直(下→上) | 水平(左→右) |
Step 2:确定每个子项的基准尺寸 flex-basis
flex-basis 定义子项在主轴方向上的理想占用空间。
计算规则如下(简化版):
- 若指定了
flex-basis,则以此为准; - 否则看
width(或主轴方向的尺寸); - 若都没有,则按内容尺寸(content size)。
例如:
.child {
flex-basis: 200px; /* 初始宽度 */
}
此时,无论容器有多宽,子项在“弹性计算”前都认为自己有 200px。
Step 3:计算剩余空间
容器会统计:
剩余空间 = 容器主轴长度 - 所有子项 flex-basis 总和 - margin 总和
- 如果 剩余空间 > 0:说明容器“还有余地”,执行
flex-grow。 - 如果 剩余空间 < 0:说明“装不下”,执行
flex-shrink。 - 如果 = 0:则不需要伸缩,按原尺寸摆放。
Step 4:按比例分配空间(核心算法)
(1)扩展阶段(grow)
当有剩余空间时:
子项实际增加的宽度 = 剩余空间 × (自身 grow 值 / 所有 grow 值之和)
示例:
.parent {
display: flex;
width: 600px;
}
.child {
flex-basis: 100px;
}
.child:nth-child(1) { flex-grow: 1; }
.child:nth-child(2) { flex-grow: 2; }
.child:nth-child(3) { flex-grow: 1; }
计算过程:
- 初始总宽度 = 100 × 3 = 300px;
- 剩余空间 = 600 - 300 = 300;
- 分配比例 = 1 : 2 : 1;
- 实际宽度 =
- 第1个:100 + 300×1/4 = 175
- 第2个:100 + 300×2/4 = 250
- 第3个:100 + 300×1/4 = 175
(2)收缩阶段(shrink)
当空间不足时:
收缩比例 = shrink × flex-basis
收缩量 = 剩余负空间 × (自身收缩比例 / 所有收缩比例之和)
即:
谁的体积大、shrink 值高,谁收得更多。
Step 5:对齐阶段(alignment)
在尺寸分配完后,还要决定“剩余空间如何对齐”,
即 justify-content(主轴) 和 align-items(交叉轴):
| justify-content 值 | 含义 |
|---|---|
| flex-start | 从起点开始(默认) |
| flex-end | 从终点开始 |
| center | 居中 |
| space-between | 两端对齐,中间平均分布 |
| space-around | 项目两侧间距相等(看似居中) |
| space-evenly | 各间距完全均等 |
形象理解:像橡皮筋一样的布局
我们可以想象容器像一根橡皮筋,子项是固定间隔的节点。
flex-basis是初始间距;flex-grow决定拉伸时各节点怎么动;flex-shrink决定压缩时谁让谁;justify-content决定整体在橡皮筋上的位置;align-items决定橡皮筋在另一维度的姿态。
这样,Flex 布局就能像“弹性肌肉”一样,
在不同窗口、不同内容大小下自动重排。
简表总结:三个属性的对比
| 属性 | 作用阶段 | 控制空间 | 默认值 | 是否继承 |
|---|---|---|---|---|
| flex-basis | 初始测量 | 固定基准尺寸 | auto | 否 |
| flex-grow | 空间充足时 | 扩展比例 | 0 | 否 |
| flex-shrink | 空间不足时 | 收缩比例 | 1 | 否 |
Flex 的本质定位
Flex 不是一种“脱流布局”,而是一种新的格式化上下文(FFC)算法。
- 它继承了 BFC 的稳定性;
- 又引入了可伸缩空间分配机制;
- 最后以
justify/align系列控制视觉分布。
Flex 交叉轴的计算机制(align-items、align-self、align-content 的工作原理 、多行时(wrap)如何分配垂直空间 )
我们现在从「主轴(main axis)」跨入到 Flex 的另一半灵魂——交叉轴(cross axis)。
这一部分的核心是:Flex 不只是能“横向伸缩”,它还能在垂直方向上自适应分配与对齐。
我们将按以下层次深入理解 👇
从“主轴”到“交叉轴”:为什么还需要第二条轴?
Flex 的最初目标是解决“一维布局”的问题。
一维意味着:它只直接处理一个方向(主轴)的空间分配。
但现实中文本与盒子往往有高度差、行高差、换行现象——
因此,Flex 引入了第二条**“交叉轴”**来处理垂直方向的排列问题。
🔹 举个例子:
.container {
display: flex;
height: 200px;
align-items: center;
}
这里的 align-items: center; 让所有子项在容器垂直方向上居中。
为什么能做到?因为此时主轴(row)负责 横向分配宽度,
而交叉轴(column)负责 纵向对齐位置。
Flex 的两条轴体系(结构认知)
在任何 Flex 容器中,都有两条逻辑轴:
| 名称 | 方向 | 负责属性 | 控制的行为 |
|---|---|---|---|
| 主轴(Main Axis) | 由 flex-direction决定 | justify-content | 控制主轴上子项分布 |
| 交叉轴(Cross Axis) | 与主轴垂直 | align-items、 align-self、 align-content | 控制子项垂直对齐方式 |
理解:
主轴 = “一行内部的排列”
交叉轴 = “各行之间、各行内部的对齐”
单行情境下:align-items / align-self 的作用
1. align-items:定义“整行子项”的对齐方式
align-items 决定容器中所有子项在交叉轴上的位置。
它作用于 当前 flex 行中的所有项目。
常用取值如下:
| 值 | 含义 | 行为 |
|---|---|---|
| stretch | 拉伸(默认) | 子项高度会被拉伸以充满容器(除非自身设定 height) |
| flex-start | 顶对齐 | 子项贴近交叉轴起点 |
| flex-end | 底对齐 | 子项贴近交叉轴终点 |
| center | 居中对齐 | 子项在交叉轴方向上垂直居中 |
| baseline | 基线对齐 | 根据文字基线对齐(常用于文字混排) |
示例:
.container {
display: flex;
align-items: baseline;
}
视觉表现上,如果各子项中包含不同大小字体的文字,
它们的“文字底线”会保持齐平。
2. align-self:单个子项的“自我覆盖”
align-self 可以覆盖 align-items 的全局规则,
相当于局部“个体例外”。
.item:nth-child(2) {
align-self: flex-end;
}
此时只有第二个元素会贴底,其他仍按 align-items 的规则排列。
这是一种 上下文优先级继承关系:
align-self → 覆盖 align-items(就近原则)
多行情境下:align-content 的作用
当我们使用 flex-wrap: wrap; 让子项换行时,
容器内部会生成 多个 flex 行(flex lines)。
此时又多出一层“行之间”的对齐需求,
这就由 align-content 来处理。
| 值 | 含义 | 行为 |
|---|---|---|
| stretch | 默认。多行平分剩余空间,使每行高度相等 | |
| flex-start | 所有行贴近交叉轴起点(上方) | |
| flex-end | 所有行贴近交叉轴终点(下方) | |
| center | 所有行在交叉轴方向上整体居中 | |
| space-between | 上下两行贴边,中间均匀分布 | |
| space-around | 行间距平均分布 | |
| space-evenly | 行与行之间、上下边距完全相等 |
直观理解:
align-items → 控制“每行内”的子项垂直对齐
align-content → 控制“多行之间”的整体垂直分布
示例:
.container {
display: flex;
flex-wrap: wrap;
height: 500px;
align-content: space-between;
}
此时多行子项会在容器高 500px 内,上下平分空间。
交叉轴的计算机制(浏览器内部逻辑)
可以形象化地看作两层循环:
For each flex line:
1. 确定每个 item 的 cross-size(高度)
2. 按 align-items / align-self 调整各 item 位置
End
在所有 flex lines 计算完毕后:
3. 根据 align-content 重新分布每一行的整体 vertical offset
所以,最终视觉位置 =
行内对齐偏移(align-items) + 行间偏移(align-content)
Flex 对 BFC 的继承与增强
Flex 容器本质上仍属于 块级格式化上下文(BFC),
但它内部创建了新的布局规则——Flex Formatting Context (FFC)。
两者关系可以这么看:
BFC
├─ 常规流 (Block Flow)
├─ 浮动层 (Float Layer)
└─ Flex Formatting Context (FFC)
├─ 主轴分配 (justify-content)
└─ 交叉轴对齐 (align-items / align-content)
Flex 并没有抛弃 BFC,而是扩展了它的“内部空间分配机制”。
类比总结
| 层级 | 控制内容 | 类比 |
|---|---|---|
| justify-content | 子项在主轴上如何分布 | 横向队列的“水平分布” |
| align-items | 每个子项在行内的垂直对齐 | 每行中“人站得高低” |
| align-self | 特殊个体站得更高/更低 | 个别人的特立独行 |
| align-content | 多行之间的整体分布 | 多排队伍在操场上的纵向分布 |