CSS排版布局篇(6):Flex 一维布局(Flexible Box Layout)

5 阅读12分钟

初步认识

为什么会出现 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 如何在排版阶段起作用(底层逻辑)

在浏览器渲染过程中:

  1. 创建 FFC:当 display:flex 出现时,容器生成新的 Flex Formatting Context;
  2. 确定主轴方向flex-direction: row/column
  3. 测量主轴尺寸:根据每个子项的初始宽高(或内容大小);
  4. 分配剩余空间:根据 flex-grow / flex-shrink / flex-basis
  5. 对齐(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 定义子项在主轴方向上的理想占用空间

计算规则如下(简化版):

  1. 若指定了 flex-basis,则以此为准;
  2. 否则看 width(或主轴方向的尺寸);
  3. 若都没有,则按内容尺寸(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多行之间的整体分布多排队伍在操场上的纵向分布