🧲 Flexbox · 简写语义与对齐边界
Flexbox 入门容易,但两条主线都藏着易被忽略的细节。其一是 flex 简写本身的语义:flex: 1 / auto / none 各展开成什么、flex-basis 与 width 谁优先、为什么 flex: 1 的 basis 是 0% 而不是 0。其二是「对齐」在边界情形下的行为:justify-content: center 一旦溢出为何把左半边永久截断、safe / unsafe 如何化解、多行 flex-wrap: balance 又如何把各行调匀。每页都用真实 getComputedStyle / getBoundingClientRect 实测呈现浏览器的实际行为。
flex-basis 的 0 vs 0% 与 vs width → 看分配过程:grow / shrink 如何按比例瓜分剩余 / 收缩亏空、由此引出 等宽列假象(flex: 1 等分的是内容盒、非列宽)、以及让 grow 正比于 basis 实现的 两端对齐图片墙 → 退一步看 width / min / max 的取值空间(length / % / 内容驱动关键字、百分比相对谁)→ 再看谁能成为 flex item:display: contents 穿透中间包裹层 → 再看排布:对齐属性全景(谁管主轴、谁管交叉轴)、gap 与缝里的分隔线、order / reverse 只改视觉序的 a11y 陷阱 → 再到边界:「居中即截断」(scrollLeft 到不了左侧)、滚动容器丢尾部 padding → 用 safe center 一行化解 → 行间维度的 flex-wrap: balance 让多行换行更均衡。flex 简写:一个值如何展开成三个
flex: 1 / auto / none / 100px 都是简写,恒定展开为 flex-grow flex-shrink flex-basis 三个全写。本页理清各关键字的展开规则、三者的默认值(0 / 1 / auto),以及单值是 <number> 还是 <width> 如何分派;切换写法用 getComputedStyle 实测浏览器真正展开的值(含 flex: 1 的 basis 实为 0%)。
flex-basis:vs width、百分比与 content 回退
flex-basis 定 item 主轴初始尺寸,有三处易错点:与 width 同时存在时 flex-basis 胜出;百分比在容器尺寸不确定时悄悄退回 content;由此 flex: 1(= 1 1 0%)里的 0% 不等于 0。用列向无定高容器 getBoundingClientRect 实测 0 vs 0% 的高度差,再切 basis/width 看实测宽度跟谁走。
flex-grow / flex-shrink:剩余与亏空空间如何分配
把各 item 的 flex-basis 求和与容器主轴一比:有剩余则 flex-grow 按比例瓜分、不足则 flex-shrink 按 shrink×basis 加权收缩,两者互斥。拖容器宽越过临界点,用预测算式与 getBoundingClientRect 实测逐项对照;并讲清 min-width: auto 下限与 wrap 为何会让 shrink「失灵」。
等宽列假象:flex: 1 给的是「内容盒等宽」,不是「列等宽」
给每列 flex: 1 做等宽栅格,一旦某列有不同的 padding / border 就明显偏宽。根因接着分配机制:flex-grow 把剩余空间等量加到各列 content box,padding / border 不计入分配、是事后叠加的——于是「内容盒都等宽,边框盒差 2×padding」。用 getBoundingClientRect 实测对照 flex: 1 与 grid 1fr:两种排布下「边框盒 / 内容盒谁等宽」正好对调,并给出 flexbox 内的嵌套补救与 grid 首选解。
两端对齐图片墙:让 flex-grow 正比于 flex-basis
相册 / 画廊版式要的「同一行等高、各图宽随宽高比、整行两端撑满」,落到 grow / basis 上只需一招:令每个 item 的 flex-basis 与 flex-grow 同时等于该图缩到统一高度时的宽度 wᵢ = H×宽/高。代入分配公式得 finalᵢ = wᵢ×(1 + free/Σw)——同行系数 k 相同,整行等价于等比缩放、行内自动等高。拖容器宽 / 基准行高,用 getBoundingClientRect 逐行实测等高;并讲清末行为何要 ::after { flex-grow: 99999 } 收尾,以及与 JS 二分方案的取舍。
尺寸取值:width / height / min / max 的各种值
退一步看尺寸属性本身能接哪些取值:固定 <length>、相对 <percentage>、内容驱动的 min-content / max-content / fit-content()、较新的 stretch。实测 width 取值之旅 + min/max 夹取;讲清百分比相对包含块哪个维度(含 padding-top:% 按宽度算、height:% 在父高不确定时退化为 auto 的陷阱)、flex item 的 min-width: auto 自动最小尺寸、逻辑尺寸 inline-size / block-size 随 writing-mode 翻转,以及与 layout 相关的长度单位(rem/ch/vw/svh/dvh/容器单位 cqw)。
display: contents:让包裹层「消失」,子元素直接成为 flex item
flex / grid 只认直接子元素。当容器与真正的内容之间隔着一层语义包裹(组件根、列表项 wrapper)时,这层会成为唯一的 flex item、把内容「闷」在里面。display: contents 让这层自身不生成盒子、子元素上移直接成为 item。实测切 block ↔ contents 看子元素如何进入 flex 行并让 flex: 1 生效;并讲清代价(自身 border/padding 失效)、a11y 历史问题、对 replaced/table 无效、vs display: none。
Flex 下的对齐属性:主轴、交叉轴与作用范围
justify-* 管主轴、align-* 管交叉轴:justify-content 分配主轴剩余空间,align-items / align-self / align-content 管交叉轴(其中 align-content 仅多行有效)。切 flex-direction / flex-wrap 与各对齐值实时看布局并回显 computed 值;并理清 justify-items / justify-self 为何在 flex 不生效、单项主轴对齐改用 margin: auto。
gap:项之间的固定间距,以及在缝里画线
gap 只落在项之间、不碰首尾、不参与剩余空间分配(先扣间距再让 grow/shrink 分配 free)——实测它与 margin 拼间距的差别。再看 CSS Gaps Module Level 1 的 gap decorations:把 multicol 的 column-rule 扩展到 flex/grid、补上 row-rule,直接在缝里画分隔线(纯视觉、不占布局),含 rule-break / repeat() 图案与特性探测降级。
order 与 reverse:只改视觉顺序,不改源码顺序
order(默认 0,升序排、同值按 DOM 序)与 flex-direction: *-reverse 都重排视觉位置,却不动 DOM / Tab 焦点 / 朗读顺序。调 order 看视觉重排、走一遍 Tab 看焦点始终按 DOM,直观呈现这条「视觉序 ≠ 源码序」裂缝——它正是 WCAG 2.4.3 Focus Order / 1.3.2 Meaningful Sequence 点名的 a11y 陷阱。
居中即截断:justify-content: center 与 overflow 的冲突
想让横向条「数量少时居中、数量多时可滚」,于是写 justify-content: center + overflow: auto——结果左半边滚不到、被永久截断。根因在 scrollLeft 只能取 [0, scrollWidth-clientWidth]、到不了负值,而居中把首项推到了内容原点左侧。拖动到溢出、点「滚到最左」实测被切的 item,再看两种常见解法:子项 margin: auto、嵌套 + max-width: 100%。
滚动容器的尾部 padding 去哪了
给会横向滚动的 flex 容器加 padding: 0 20px,滚到最右时尾部那 20px 常常不见了——根因是历史上浏览器算 scrollWidth 时把 padding-inline-end 排除在外,它落在滚不到的区域。滚到两端用 getBoundingClientRect 实测当前浏览器首尾留白,再给三种绕法(末尾 ::after 占位 / 末项 margin / border 代替)。与居中即截断同族、不同因。
safe / unsafe:给对齐加一句「别切到内容」
Box Alignment 的 <overflow-position> 修饰位:safe center =「优先居中,但若会溢出丢失就退回 start」,正是「居中即截断」的简洁解;unsafe center =「即使切到内容也保持居中」。同一条轨道切换三种关键字实测可达性,回显 getComputedStyle 生效值;再讲它在 align-items/align-content/justify-self 的适用范围,以及 space-between fallback 不退回 start 的反直觉行为(csswg #11937)。
flex-wrap: balance:让换行分布更均衡
flex-wrap: wrap 的换行是贪心的——尽量填满当前行,末行常常只剩一两项。提案中的 flex-wrap: balance(csswg #3070)类比 text-wrap: balance,用 Knuth–Plass 式断行让各行长度尽量接近。左右双列对照贪心 vs 均衡(各行标注实测宽度),观察末行如何被调匀;并理清它与 justify-content 分属行间 vs 行内两件事。原生尚未实现,本页用 JS 模拟并加以说明。
safe 表达「不要切到内容」、balance 表达「各行调匀」)。理解这一层后,这类现象就都有了清晰的解释。🔗 相关链接
📐 规范 / 文档
-
CSS Flexible Box Layout Module Level 1
· W3C / CSSWG
整个系列的规范本体:flex container / item 模型、
flex简写与 grow/shrink/basis 的分配算法、min-width: auto自动最小尺寸的定义。 - MDN · CSS flexible box layout(总览) · developer.mozilla.org flexbox 模块总入口:两条轴、容器与 item 属性的概念地图,以及 Basic concepts 等系列指南。
-
Can I use · CSS Flexible Box Layout
· caniuse.com
display: flex与各flex-*/ 对齐属性的实时浏览器支持矩阵。 -
MDN · flex(简写,含各关键字展开)
· developer.mozilla.org
shorthand 页的依据:
initial/auto/none/<number>的展开规则与「浏览器用0%而非0」的告警。 -
MDN · flex-basis(含 0 vs 0% 示例)
· developer.mozilla.org
basis 页的依据:百分比解析与
content回退、vswidth优先级、0 vs 0% 的可运行示例。 - MDN · <percentage> 取值 · developer.mozilla.org sizing 页的依据:百分比相对包含块哪个维度解析,以及 margin/padding 百分比一律按 inline 尺寸。
-
MDN · width(全部取值)
· developer.mozilla.org
sizing 页的依据:
min-content/max-content/fit-content()/stretch的定义与 min/max 夹取。 -
MDN · CSS Logical Properties and Values
· developer.mozilla.org
sizing 页逻辑尺寸一节的依据:
inline-size/block-size与物理width/height随writing-mode的映射。 -
MDN · <length> 单位
· developer.mozilla.org
sizing 页长度单位一节的依据:字体相对(
rem/em/ch)、视口(vw/svh/dvh)、容器(cqw/cqi)各自的参照系。 - MDN · display: contents · developer.mozilla.org contents 页的依据:元素不生成盒子、子元素上移成为 item,以及 a11y 与适用元素的注意点。
-
CSS Gaps Module Level 1
· W3C / CSSWG
gap 页的依据:
gap间距与 gap decorations(column-rule/row-rule/rule-break/repeat())的规范全文。 -
CSS gap decorations
· developer.chrome.com
gap 页 decorations 一节的依据:Chrome 团队对
column-rule/row-rule/repeat()的介绍与实验旗标说明。 - CSS Box Alignment · <overflow-position> · W3C / CSSWG safe / unsafe 关键字的权威定义,以及各对齐属性如何接受它。
-
MDN · justify-content(含 safe/unsafe)
· developer.mozilla.org
取值说明与示例,
<overflow-position>的用法在此。 - Can I use · justify-content safe/unsafe(flex) · caniuse.com flex 上下文里 safe/unsafe 的实时支持矩阵(以它和各页顶部横幅实测为准)。
- [css-flexbox-2] Add flex-wrap: balance · csswg-drafts #3070 flex-wrap: balance 的提案讨论串,需求与算法取向的来龙去脉。
-
space-between 的 fallback 不退回 start
· csswg-drafts #11937
为什么
space-between溢出时反而可能切到首项 —— CSSWG 承认的设计缺陷。
🎨 实例与文章(本系列灵感来源)
- flex 布局下居中溢出滚动截断问题 · chokcoco / iCSS #249 本系列「居中即截断」一页的直接出处:问题复现与 safe center / margin:auto / 嵌套三种解法。
-
Equal Columns with Flexbox: It's More Complicated Than You Might Think
· css-tricks.com
「等宽列假象」一页的直接出处:
flex: 1按 content box 等分、padding 让 border box 偏宽,以及为何 grid1fr更适合等宽栅格。 -
Flexbox and padding
· chenhuijing.com
「滚动容器丢尾部 padding」一页的直接出处:
padding-inline-end不计入scrollWidth的来龙去脉与 border / 伪元素绕法。 - Perrotin 画廊 · 图片墙版式 · perrotin.com 「两端对齐图片墙」一页的效果出处:整行等高、宽度按宽高比、两端撑满的画廊版式。
-
justified gallery · CSS Flex 极简实现
· JS Bin
「两端对齐图片墙」一页的直接出处:
flex-grow与flex-basis取同一个H×比例+::after { flex-grow: 99999 }收尾末行的写法。 -
flex-wrap: balance 实例 demo
· CodePen / yisi
可直接拖拽体验的
flex-wrap: balance演示,对照本系列 balance 页的 JS 模拟看直观效果。 - bfgeek / flex-wrap-balance explainer · github.com Chromium 布局团队的提案说明:Knuth–Plass 式断行如何套到 flex item 上。
-
UnoCSS Interactive Docs
· unocss.dev
原子类 ↔ CSS 速查:搜
flex-1/basis-/shrink-0等即可看到对应的 flex 简写/全写声明,对照本系列前两页理解工具类背后的展开。