CSS探索系列之flex布局
浏览器仍按 CSS 基础框盒模型(CSS basic box model) 把元素画成矩形:内容、内边距、边框、外边距。
Flex 改的不是盒模型本身,而是这些盒子沿一根轴怎么排、剩余空间怎么分。
传统方案靠 display、position、float 拼布局,垂直居中、等分栏都很别扭。弹性盒子布局(Flexible Box Layout,Flex) 是面向 UI 的一维布局:弹性项目可沿任意方向排布,既能撑满剩余空间,也能收缩以免撑破容器,主轴和交叉轴上的对齐都能直接控制。
基本概念
Flex 一次只处理一个维度,一行或一列。要同时管行和列,用 网格布局。

.container {
display: flex; /* 块级弹性容器 */
}
.container {
display: inline-flex; /* 行内级弹性容器 */
}
display: flex、inline-flex 会建立弹性格式化上下文(Flex Formatting Context,FFC),不是 BFC。容器叫弹性容器,它的每个子元素都是弹性项目(含 ::before、::after)。文本节点会生成匿名弹性项目。绝对定位的子元素不参与弹性排版。
一套弹性布局有两根轴:
- 主轴(main axis):项目依次排列的方向,由
flex-direction决定。 - 交叉轴(cross axis,也称侧轴):垂直于主轴的那根轴。
刚建成的弹性容器,项目有一套默认行为:沿主轴排成一行(flex-direction: row)、从主轴起点起排、主轴方向默认不放大但可缩小、交叉轴默认被拉齐、flex-basis: auto、flex-wrap: nowrap。项目过多时默认溢出,不换行。
下列属性在弹性布局里失去原意:
- 多栏模块的
column-*对弹性容器无效。 float与clear对弹性项目无效,不会把项目拿出弹性布局。vertical-align对弹性项目的对齐无效。
容器的属性
设置在弹性容器上:
flex-directionflex-wrapflex-flowjustify-contentalign-itemsalign-contentplace-contentgap、row-gap、column-gap
flex-direction
定义主轴方向,也就是项目的排列方向。
初始值:row
row:主轴沿行向(inline),从主轴起点到终点。row-reverse:主轴沿行向,起点与终点对调。column:主轴沿块向(block),从块起点到块终点。column-reverse:主轴沿块向,起点与终点对调。

排列方向:
row、row-reverse:沿行内轴排布,受writing-mode、direction共同影响。- 在
horizontal-tb模式中行内轴为水平方向; direction: ltr时row从左到右,direction: rtl时row从右到左,row-reverse顺序相反。- 在竖排模式下,行内轴切换为垂直方向,
row不再是横向排列。
- 在
column、column-reverse:沿块轴排布,排布方向仅由writing-mode决定,不受direction影响。horizontal-tb:块轴自上而下,column从上至下排列;vertical-rl:块轴自右向左,column从容器右侧向左侧依次排列;vertical-lr:块轴自左向右,column从容器左侧向右侧依次排列;column-reverse为对应方向反转。
flex-wrap
指定项目单行还是多行。多行时,它还决定行在交叉轴上的堆叠方向。
初始值:nowrap
nowrap:所有项目放在一行,挤不下就溢出或按flex-shrink收缩。wrap:换行,新行沿交叉轴正方向堆叠(水平书写时由上往下)。wrap-reverse:换行,新行沿交叉轴反方向堆叠(水平书写时由下往上),交叉轴起点与终点对调。

flex-flow
flex-direction 和 flex-wrap 的简写。初始值:row nowrap。
flex-flow: column-reverse wrap;
justify-content
定义项目在主轴上的对齐,以及主轴剩余空间怎么分。
初始值:normal。在弹性布局里表现与 flex-start 相同。
常用值:
flex-start:靠主轴起点。flex-end:靠主轴终点。center:主轴居中。space-between:两端贴边,项目之间间隔相等。space-around:每个项目两侧间隔相等,因此项目之间的间隔是项目与容器边缘间隔的两倍。space-evenly:项目之间、项目与容器边缘之间的间隔都相等。
其余取值(存在浏览器兼容限制):
start、end:逻辑对齐,跟随书写方向起点、终点,不绑定 Flex 主轴;flex-start、flex-end严格跟随主轴方向。旧版 Safari、IE11 不支持。left、right:物理左、右对齐。主轴为垂直方向(column、column-reverse)时,无法匹配水平物理轴,自动按start处理。stretch:仅在 Grid 主轴有效;弹性布局主轴上不生效,尺寸由 flex 伸缩算法决定。safe、unsafe:溢出修饰符,必须搭配对齐关键字使用。safe在内容溢出容器时自动降级为起点对齐;unsafe坚持目标对齐,允许内容溢出。需要较高浏览器版本,移动端低版本 Safari、小程序谨慎使用。

备注: 弹性项目的 margin: auto 会先吃掉主轴(或交叉轴)上的剩余空间,再轮到 justify-content、align-items。主轴两侧都写成 auto,项目会在主轴上居中。
align-items
定义项目在交叉轴上如何对齐。
初始值:normal。在弹性布局里表现与 stretch 相同。
常用值:
flex-start:靠交叉轴起点。flex-end:靠交叉轴终点。center:交叉轴居中。baseline:按第一行文字基线对齐。stretch:项目在交叉轴上拉伸填满容器;前提是项目交叉轴尺寸为auto,且没有限制拉伸。
其余取值(存在浏览器兼容限制):
start、end:逻辑对齐,跟随容器书写方向起点、终点,不绑定 Flex 交叉轴;flex-start、flex-end严格跟随交叉轴。旧版 Safari、IE11 不支持。self-start、self-end:跟随单个弹性项目自身的书写方向对齐。旧版 Safari、IE11 不支持。first baseline、last baseline:显式指定使用第一基线、最后基线对齐;通用简写baseline等效于first baseline,兼容性更好。旧版 Safari、IE11 不支持。safe、unsafe:溢出修饰符,必须搭配对齐关键字使用。含义与justify-content相同。需要较高浏览器版本,移动端低版本 Safari、小程序谨慎使用。

align-content
弹性容器分多行时,定义各行在交叉轴上的对齐和空间分配。单行容器(flex-wrap: nowrap,或项目并未换行)上无效。
初始值:normal。在弹性布局里表现与 stretch 相同。
常用值:
flex-start:各行靠交叉轴起点。flex-end:各行靠交叉轴终点。center:各行整体居中。space-between:首尾行贴边,行与行间隔平均。space-around:每行两侧间隔相等,行与行之间的间隔是行与边缘间隔的两倍。space-evenly:行与行、行与边缘的间隔都相等。stretch:各行被拉大,占满交叉轴。
其余取值(存在浏览器兼容限制):
start、end:逻辑对齐,跟随书写方向起点、终点,不绑定 Flex 交叉轴。旧版 Safari、IE11 不支持。baseline、first baseline、last baseline:按各行基线对齐。旧版 Safari、IE11 不支持。safe、unsafe:溢出修饰符,必须搭配对齐关键字使用。含义与justify-content相同。需要较高浏览器版本,移动端低版本 Safari、小程序谨慎使用。

place-content
align-content 和 justify-content 的简写:第一个值给交叉轴,第二个值给主轴。取值分别沿用这两个属性的常用值、其余取值。
place-content: center space-between;
注意: 只写一个值时,若该值对两个属性都合法,则两个属性都用它;对其中任何一个不合法,整条声明无效。
其余取值(存在浏览器兼容限制):
- 属性本身:IE11、旧版 Edge 不支持;Safari 9 起可用。
- 写成
start、end、safe等时,兼容限制与align-content、justify-content的其余取值相同。需要兼容旧环境时,拆开写align-content和justify-content的常用值。
gap
gap(以及 row-gap、column-gap)设置项目之间的间距,不作用到容器内边缘。flex-direction: row 时,主轴间隔对应 column-gap,交叉轴上行与行的间隔对应 row-gap;flex-direction: column 时对调。gap 不计入 flex-basis,用百分比分栏时要把间隙从项目尺寸里扣掉。
常用值:长度、百分比,如 gap: 16px、gap: 8px 12px。
属性浏览器兼容:弹性容器上的 gap,IE11 不支持;Safari 需 14.1+(iOS Safari 14.5+)。
项目的属性
设置在弹性项目上:
orderflex-growflex-shrinkflex-basisflexalign-self
order
定义项目的视觉排列顺序。数值越小越靠前,默认 0,可取负整数。只改视觉顺序,不改源码顺序、Tab 顺序和读屏顺序。
flex-grow
指定项目怎么分主轴上的剩余空间。剩余空间 = 容器主轴内尺寸 − 该行所有项目的主轴尺寸之和。
初始值:0(有剩余空间也不放大)。大于 0 才参与分配。负值无效。
- 把所有参与放大的
flex-grow加总,记为总和。 - 总和 ≥ 1:剩余空间分光,每项分到的份额按各自
flex-grow占总和的比例。三个项目为1 : 2 : 1时,值为2的拿到剩余空间的2/4。 - 总和 < 1:只分配「剩余空间 × 总和」那么多,容器里仍留空。只有一项且值为
0.5,它只吃掉一半剩余空间。 - 只有一项且值 ≥ 1:它吃掉全部剩余空间。

flex-shrink
指定项目在主轴空间不够时怎么收缩。默认会收缩;flex-wrap: wrap 换行之后,每一行单独算,不一定再收缩。
不足空间 = 该行项目主轴尺寸之和 − 容器主轴内尺寸。
初始值:1。0 表示不收缩。负值无效。
收缩不是按 flex-shrink 值直接等比,而是按加权收缩因子:
加权收缩因子 = flex-shrink × 该项目的弹性基准尺寸(inner flex base size)
某项收掉的不足空间 = 不足空间 × (该项加权因子 / 所有项加权因子之和)
同样是 flex-shrink: 1,基准尺寸更大的项目会多收一截,避免小项目先被收到 0。
- 所有加权因子之和对应的收缩比例 < 1(各
flex-shrink之和 < 1,且按上面公式折算后仍吃不完)时,不足空间不会被完全消化,容器仍溢出。 - 一项
flex-shrink: 0、其余为1:空间不足时前者不缩小。

flex-basis
指定项目在主轴上的初始主尺寸(弹性基准)。浏览器先按它算出有没有剩余空间,再做 grow、shrink。
初始值:auto
常用值:
auto:先看主轴方向上的width(flex-direction: column时看height)。那边也是auto,就按内容尺寸,等价于content。<width>:与width、height相同的长度或百分比(如350px、2rem、30%)。负值无效。百分比相对弹性容器的主轴内尺寸;容器主轴尺寸不定时,百分比按content解析。
其余取值(存在浏览器兼容限制):
content:按内容自动确定基准,忽略width、height。旧版 Safari、IE11 不支持。max-content、min-content、fit-content:按固有尺寸关键词确定基准。旧版 Safari、IE11 不支持。需要兼容时,用auto或明确的长度代替。
注意: flex-basis 不为 auto 时,它覆盖主轴方向上的 width(或 height)。最终用到的主尺寸还要经过 grow、shrink 和 min、max 限制,见下文「弹性项目尺寸的计算」。
早期规范一度把 auto 改成「自动尺寸」、另造 main-size 去参照宽高,后来撤销。现在的分工就是:auto 参照宽高,content 表示按内容自动尺寸。
flex
flex-grow、flex-shrink、flex-basis 的简写,后两项可省略。
初始值:0 1 auto,关键字 initial 即此值。
- 一个值
- 无单位数字:当作
flex-grow。规范展开为n 1 0;现行浏览器把省略的基准写成0%。容器主轴尺寸不定时,0%会按content解析,和长度0不同。 - 合法的宽度值:当作
flex-basis,grow、shrink 分别为1。 auto:1 1 autonone:0 0 auto(不放大、不缩小)initial:0 1 auto
- 无单位数字:当作
- 两个值:第一个必须是无单位数字,当作
flex-grow。第二个要么是无单位数字(flex-shrink),要么是宽度值(flex-basis)。 - 三个值:grow、shrink、basis,按这个顺序。
单独写 flex-grow: 1 时,另外两项仍是初始值 0 1 auto。写成 flex: 1 时,没写出的分量按简写规则补全,和初始值不是同一套。要跟内容宽度挂钩再分剩余空间,用 flex: auto。
align-self
覆盖单个项目的交叉轴对齐,把容器的 align-items 顶掉。
初始值:auto,表示用父元素的 align-items;没有弹性父元素时按 stretch。
常用值:
auto:继承容器的align-items。flex-start、flex-end、center、baseline、stretch:含义与align-items的常用值相同。
其余取值(存在浏览器兼容限制):
start、end、self-start、self-end、first baseline、last baseline、safe、unsafe:含义与兼容限制与align-items的其余取值相同。旧版 Safari、IE11 不支持。

弹性项目尺寸的计算
最终主轴尺寸是三层一起算的结果:
- 基础尺寸:
flex-basis、主轴方向的width、height、内容尺寸、box-sizing。 - 弹性增长或收缩:
flex-grow、flex-shrink。 - 最大最小限制:
min-width、max-width(或对应的 height),以及内容最小尺寸。
最大最小尺寸限制 > 弹性增长或收缩 > 基础尺寸
计算顺序:
- 未设置
flex-basis时,基准等于主轴方向的width(或height)。 flex-basis和width都未设置时,基准等于内容尺寸。- 默认
min-width: auto(主轴水平时),项目不会收缩到比内容最小尺寸更小。要比内容尺寸更小,设置min-width: 0,或让该项目的overflow不为visible。 - 主轴有剩余空间走
flex-grow;空间不足走flex-shrink。
text-overflow: ellipsis 失效
弹性项目里写了 text-overflow: ellipsis,省略号经常不出现:
<section>
<div class="item">低眉含笑间</div>
<div class="item">
<div class="ellipsis">
寒灯纸上梨花雨凉 谁的深情绚烂了三生石上的一见倾心 灯烛依旧 无人执手。低眉含笑间 寒灯纸上梨花雨凉 谁的深情绚烂了三生石上的一见倾心 灯烛依旧 无人执手
</div>
</div>
</section>
section {
display: flex;
width: 800px;
margin: 0 auto;
padding: 10px;
border: 1px solid #0aa;
}
.item {
padding: 10px;
color: #fff;
text-align: center;
background-color: #0aa;
white-space: nowrap;
}
.item:not(:last-child) {
margin-right: 15px;
}
.ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

text-overflow: ellipsis 生效要同时满足:
white-space: nowrap:强制不换行;overflow: hidden:溢出隐藏;text-overflow: ellipsis:显示省略号;- 盒子的宽度必须小于文本固有宽度,内容才能溢出。
给 .ellipsis 写死一个比文本更小的 width 能让省略号显示,但不适合响应式。真正卡住的是弹性项目:默认 min-width: auto 把下限钉在内容最小尺寸上;内部 .ellipsis 又是 white-space: nowrap,内容最小尺寸就是整段文字的宽度。
结果是:
弹性项目的宽度 = `.ellipsis` 的宽度 = 文本内容宽度。文本没有溢出,ellipsis 不触发。
三种解决方案(作用在弹性项目上,不是作用在 .ellipsis 上):
- 设置
overflow不为visible:自动最小尺寸按0处理,项目可以被收到容器分给它的可用宽度以内。 - 设置
flex: 1; width: 0:主轴尺寸不按内容固有宽度起算,最终宽度仍由flex算法和 min、max 一起算。 - 设置
min-width: 0:普通元素的min-width初始值是0,弹性项目改成auto,以免默认把内容挤没。写成0就是把自动下限关掉。宽度按min(max(最小宽度, 可用宽度), 首选宽度)计算。
<div class="item" style="overflow: hidden;">...</div>
<div class="item" style="flex: 1; width: 0;">...</div>
<div class="item" style="min-width: 0;">...</div>