Flexibility布局流水线深度解析:flex-grow与flex-shrink如何一步步分配空间
【免费下载链接】flexibilityA JavaScript polyfill for Flexbox项目地址: https://gitcode.com/gh_mirrors/fl/flexibility
Flexibility 是一个用 JavaScript 实现的 Flexbox 布局 polyfill,它在旧浏览器中模拟弹性盒的自动布局能力。本文将带你深入它的布局流水线,看懂 flex-grow(伸展)与 flex-shrink(收缩)是如何一步步把剩余空间或溢出空间按比例分配给每个子项的——无需手写任何计算逻辑。
一、为什么需要 Flexibility 布局 polyfill
原生display: flex在旧版 IE 等浏览器中不被支持,而 Flexbox 的核心魅力在于:即使子项尺寸未知或动态变化,容器也能自动布局、对齐并分配空间。
Flexibility 的思路很直接:用 JavaScript 读取容器的样式,按 Flexbox 规范完成全部计算,再把结果以width、margin等普通样式写回页面。对使用者来说,你只需像写原生 Flexbox 一样写 CSS:
.container { -js-display: flex; /* 触发 polyfill 识别 */ display: flex; }核心关键词速记:Flexibility = Flexbox 布局 + flex-grow / flex-shrink 空间分配 + 老浏览器兼容。
二、布局流水线总览:读取 → 计算 → 写回
打开入口文件 lib/index.js,你会发现整个 polyfill 的核心只有一行:
writeAll(readAll(target));也就是说,Flexibility 的流水线分为三段:
| 阶段 | 职责 | 关键文件 |
|---|---|---|
| ① 读取(read) | 收集容器与子项的 flex 相关样式 | lib/read/index.js |
| ② 计算(flexbox) | 按 Flexbox 规范逐步分配主轴空间 | lib/flexbox/index.js |
| ③ 写回(write) | 把计算好的尺寸、外边距写回 DOM | lib/write/index.js |
真正决定"空间怎么分"的是第②步。下面按执行顺序拆解,重点看 flex-grow 与 flex-shrink 的分工。
三、步骤 1:flex-direction 统一坐标系
分配空间之前,必须先确定"主轴是谁"。lib/flexbox/flex-direction/index.js 会检查容器的flex-direction:
- 值为
row/row-reverse:主轴是水平方向,width、margin-left/right都归入主轴; - 值为
column/column-reverse:主轴是垂直方向,height、margin-top/bottom归入主轴。
它的巧妙之处在于:先用子项的轴建立坐标系,再用容器自身的轴交换一次(main ↔ cross)。这样后续所有模块(包括 flex-grow / flex-shrink)都不用关心方向,只处理抽象的main值。
这一步的输出是每个节点上都挂好了一份flexStyle,包含main(主轴尺寸)、mainBefore / mainAfter(外边距)、mainOffset(实际渲染宽度)等字段。
四、步骤 2:flexbox-lines 分行并算出 mainSpace
lib/flexbox/flexbox-lines/index.js 负责把子项排成"行"(line):
- 默认创建第一行,逐个累加子项的
mainOuter(含外边距的主轴尺寸); - 如果容器允许换行(
flex-wrap: wrap)且当前行装不下新子项,就新开一行; - 统计出各行占用,并推导出容器的实际尺寸。
真正为 flex-grow / flex-shrink 做铺垫的,是在 lib/flexbox/index.js 中对每一行计算出一个关键数值——mainSpace(剩余主轴空间):
line.mainSpace = 容器主轴尺寸 - 容器内边距(paddings) - 容器边框(borders) - 本行所有子项占用的 main;这个正负号就是分水岭:
mainSpace > 0:容器装不满,有空余 → 交给 flex-grow 分;mainSpace < 0:容器装不下,溢出 → 交给 flex-shrink 收;mainSpace = 0:刚刚好,两者都不参与。
五、步骤 3:flex-grow 按比例分配剩余空间 📈
当一行有空余时,lib/flexbox/flex-grow/index.js 执行以下逻辑:
- 求总份数:
growFactor = 所有子项 flex-grow 之和;若为 0(没人想长大),直接跳过; - 按比例分发:对每个子项,追加的尺寸 =
flexGrow ÷ growFactor × mainSpace,即"你的份数 ÷ 总份数 × 空余"; - 特判 auto:如果子项主轴尺寸是
auto(未写死),则用实际渲染宽度mainOffset作为基准再叠加增长量; - 清零:全部加完后把
line.mainSpace置为0,表示空间已分完。
一个直观例子:一行 900px 空余,三个子项flex-grow分别为 1、2、3(总份数 6),它们依次获得 150px、300px、450px。比例直觉,一目了然。
六、步骤 4:flex-shrink 按比例分摊溢出 📉
空间不够时,lib/flexbox/flex-shrink/index.js 用完全对称的算法"反向操作":
- 求总份数:
shrinkFactor = 所有子项 flex-shrink 之和; - 按比例扣减:每个子项的尺寸 +=
flexShrink ÷ shrinkFactor × mainSpace。注意此时mainSpace是负数,所以"加上负数"就是缩小; - 清零:分完溢出后同样把
mainSpace归零。
可以看到 grow 与 shrink 是同一套"按权重比例分摊"思想的两次应用,只是方向相反——这就是 Flexbox 弹性布局的核心机制。
七、完整执行顺序与配套模块
在 lib/flexbox/index.js 中,各模块的调用顺序是精心安排的:
| 顺序 | 模块 | 作用 |
|---|---|---|
| 1 | flex-direction | 建立主轴坐标系 |
| 2 | order | 按order属性重排子项 |
| 3 | flexbox-lines | 分行、算出 mainSpace |
| 4 | align-content | 分配多行间的交叉轴空间 |
| 5 | flex-grow | 空余 → 伸展 |
| 6 | flex-shrink | 溢出 → 收缩 |
| 7 | margin-main / margin-cross | 处理auto外边距 |
| 8 | justify-content / align-items | 最终对齐 |
先分空间、再处理 auto 外边距、最后对齐——顺序保证每一步拿到的都是上一步的最新结果。
想眼见为实?仓库的test/目录提供了一组可视化用例,如 test/flex-direction-row.html、test/flex-within-flex.html,配套的 test/shared/script.js 会在页面上运行 polyfill,与.control.html对照文件对照验证。
八、三步上手 Flexibility 空间分配
- 引入编译产物 flexibility.js:
<script src="flexibility.js"></script>; - 给容器加
-js-display: flex;(或直接写display: flex); - 对最外层元素调用一次:
flexibility(document.documentElement);。
之后所有flex-grow、flex-shrink、flex-wrap都会像原生 Flexbox 一样工作,包括嵌套 flex 与负外边距场景(参考 test/negative-margin.html)。
九、小结
- Flexibility 用
readAll → flexbox 计算 → writeAll三段式流水线复现了浏览器原生的 Flexbox 布局; - flex-grow 处理
mainSpace > 0:按"份数占比"瓜分空余空间; - flex-shrink 处理
mainSpace < 0:用同一套比例算法反向扣减溢出; - 理解
mainSpace这个正负值,就理解了弹性空间分配的钥匙。🔑
更多模块设计细节可继续查看 lib/README.md 与 lib/flexbox/README.md。
【免费下载链接】flexibilityA JavaScript polyfill for Flexbox项目地址: https://gitcode.com/gh_mirrors/fl/flexibility
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考