验证 flex-grow 与 flex-shrink 的计算公式
背景简介
关于 flex-grow 与 flex-shrink 的计算公式,网上已经有很多详细的分析文章了,这里不再阐述原理,只用一些简单的 js 函数来模拟计算过程。
这里放几篇介绍原理的文章:
- https://blog.csdn.net/Snoopyqiuer/article/details/102472358
- https://juejin.cn/post/6844904016439148551#heading-5
- https://juejin.cn/post/6844904153634832392
概括
与 grow 和 shrink 计算相关的属性有:
- 父元素和子元素都要考虑:marigin/padding/border/box-sizing,
- 仅子元素需要考虑:flex-basis(会覆盖子项的 width),以及 max-width(仅针对 grow),min-wdith(仅针对 shrink)
代码示例
本例仅计算以子项的 width 和对应的 grow/shrink 为依据的实际宽度,并未考虑 marigin/padding/border/box-sizing/flex-basis/max-width/min-wdith 等属性。
其中,grow 之和 > 1 和 shrink 之和 > 1 分组的中间一项,grow 和 shrink 分别是 0;其他子项都设置了非 0 值。
flex-shrink
HTML:
<!-- grow 之和 > 1 -->
<div class="container grow grow1">
<div class="content">哈哈哈哈哈哈哈</div>
<div class="content">哈哈哈哈哈哈哈</div>
<div class="content">哈哈哈哈哈哈哈</div>
</div>
<!-- grow 之和 = 1 -->
<div class="container grow grow2">
<div class="content">哈哈哈哈哈哈哈</div>
<div class="content">哈哈哈哈哈哈哈</div>
<div class="content">哈哈哈哈哈哈哈</div>
</div>
<!-- grow 之和 < 1 -->
<div class="container grow grow3">
<div class="content">哈哈哈哈哈哈哈</div>
<div class="content">哈哈哈哈哈哈哈</div>
<div class="content">哈哈哈哈哈哈哈</div>
</div>
<!-- shrink 之和 > 1 -->
<div class="container shrink shrink1">
<div class="content">哈哈哈哈哈哈哈哈哈哈或或或或或或或或</div>
<div class="content">哈哈哈哈哈哈哈哈哈哈或或或或或或或或</div>
<div class="content">哈哈哈哈哈哈哈哈哈哈或或或或或或或或</div>
</div>
<!-- shrink 之和 = 1 -->
<div class="container shrink shrink2">
<div class="content">哈哈哈哈哈哈哈哈哈哈或或或或或或或或</div>
<div class="content">哈哈哈哈哈哈哈哈哈哈或或或或或或或或</div>
<div class="content">哈哈哈哈哈哈哈哈哈哈或或或或或或或或</div>
</div>
<!-- shrink 之和 < 1 -->
<div class="container shrink shrink3">
<div class="content">哈哈哈哈哈哈哈哈哈哈或或或或或或或或</div>
<div class="content">哈哈哈哈哈哈哈哈哈哈或或或或或或或或</div>
<div class="content">哈哈哈哈哈哈哈哈哈哈或或或或或或或或</div>
</div>
CSSS:
展开查看
* { margin: 0; padding: 0; }
.container {
display: flex;
flex-flow: no-wrap row;
margin-bottom: 40px;
width: 400px;
}.content {
width: 400px;
}.grow {
max-width: 450px;
}/* grow 之和 > 1 */
.grow1>.content:nth-child(1) {
flex-grow: 2;
width: 120px;
}.grow1>.content:nth-child(2) {
flex-grow: 0;
width: 80px;
}.grow1>.content:nth-child(3) {
flex-grow: 3;
width: 160px;
}/* grow 之和 = 1 */
.grow2>.content:nth-child(1) {
flex-grow: 0.2;
width: 120px;
}.grow2>.content:nth-child(2) {
flex-grow: 0.5;
width: 80px;
}.grow2>.content:nth-child(3) {
flex-grow: 0.3;
width: 160px;
}/* grow 之和 < 1 */
.grow3>.content:nth-child(1) {
flex-grow: 0.2;
width: 120px;
}.grow3>.content:nth-child(2) {
flex-grow: 0.1;
width: 80px;
}.grow3>.content:nth-child(3) {
flex-grow: 0.3;
width: 160px;
}/* shrink 之和 > 1 */
.shrink1>.content:nth-child(1) {
width: 120px;
flex-shrink: 2;
}.shrink1>.content:nth-child(2) {
width: 150px;
flex-shrink: 0;
}.shrink1>.content:nth-child(3) {
width: 200px;
flex-shrink: 3;
}/* shrink 之和 = 1 */
.shrink2>.content:nth-child(1) {
width: 120px;
flex-shrink: 0.2;
}.shrink2>.content:nth-child(2) {
width: 150px;
flex-shrink: 0.5;
}.shrink2>.content:nth-child(3) {
width: 200px;
flex-shrink: 0.3;
}/* shrink 之和 < 1 */
.shrink3>.content:nth-child(1) {
width: 120px;
flex-shrink: 0.2;
}.shrink3>.content:nth-child(2) {
width: 150px;
flex-shrink: 0.1;
}
.shrink3>.content:nth-child(3) {
width: 200px;
flex-shrink: 0.3;
}
Javascript:
展开查看
// 弹性盒子子元素 class FlexItemElement { width = 0; grow = 0; shrink = 1; constructor(width = 0, grow = 0, shrink = 1) { this.width = width; this.grow = grow; this.shrink = shrink; } }
// 计算子元素总权重,总收缩比与总宽度
function getParams(elements, flexType) {
let totalChildWidth = 0,
totalWeight = 0, // 元素的 width * grow/shrink 之和
totalFlex = 0; // 元素的 grow 或 shrink 之和
if (flexType) {
elements.forEach(element => {
let { [flexType]: elementFlex, width } = element;
totalWeight += elementFlex * width;
totalFlex += elementFlex;
});
} else {
elements.forEach(element => {
totalChildWidth += element.width;
});
}
return {
totalChildWidth, totalWeight, totalFlex
}
}
// 计算子元素 shrink 后的实际宽度
// flexType 用于指定 grow 或 shrink
function getFlexWidth(elements, parentWidth) {
if (elements.length > 0) {
// 判定当前需要 grow 还是 shrink
let flexType = '';
let { totalChildWidth } = getParams(elements);
let gap = parentWidth - totalChildWidth;
flexType = gap > 0 ? 'grow' : gap < 0 ? 'shrink' : '';let { totalWeight, totalFlex } = getParams(elements, flexType); let results = []; gap = Math.abs(gap); // grow 或 shrink 之和小于1,则只取差值的一部分 if (totalFlex < 1) { gap *= totalFlex; } elements.forEach(element => { let flexWidth = element.width; if (element[flexType] !== 0) { // grow 或 shrink 不为0 switch (flexType) { case 'grow': flexWidth = element.width + gap * element[flexType] / totalFlex; break; case 'shrink': flexWidth = element.width - gap * element.width * element[flexType] / totalWeight; break; default: break; // 不需要调整 } } results.push(+(flexWidth).toFixed(2)); }); console.log(`totalFlex: ${totalFlex}`); return results }
}
// main
let parentWidth = 400;
// grow test
let growElements1 = [new FlexItemElement(120, 2), new FlexItemElement(80), new FlexItemElement(160, 3)];
let growElements2 = [new FlexItemElement(120, 0.2), new FlexItemElement(80, 0.5), new FlexItemElement(160, 0.3)];
let growElements3 = [new FlexItemElement(120, 0.2), new FlexItemElement(80, 0.1), new FlexItemElement(160, 0.3)];
console.log(getFlexWidth(growElements1, parentWidth));
console.log(getFlexWidth(growElements2, parentWidth));
console.log(getFlexWidth(growElements3, parentWidth));
// shrink test
let shrinkElements1 = [new FlexItemElement(120, 0, 2), new FlexItemElement(150, 0, 0), new FlexItemElement(200, 0, 3)];
let shrinkElements2 = [new FlexItemElement(120, 0, 0.2), new FlexItemElement(150, 0, 0.5), new FlexItemElement(200, 0, 0.3)];
let shrinkElements3 = [new FlexItemElement(120, 0, 0.2), new FlexItemElement(150, 0, 0.1), new FlexItemElement(200, 0, 0.3)];
console.log(getFlexWidth(shrinkElements1, parentWidth));
console.log(getFlexWidth(shrinkElements2, parentWidth));
console.log(getFlexWidth(shrinkElements3, parentWidth));