关于flex-shrink计算:再深入一点点

1,040 阅读4分钟

引子

有关于 flex-shrinkflex-grow 属性,是如何计算的,前人已经有了很多总结,本篇是在本人学习flex过程中遇到问题后,再进行思考,对flex-shrink属性的进一步拓展。

总结前人的flex-shrink计算公式

首先看一下主要的HTML结构:

<section class='container'>
    <div class='item'></div>
    <div class='item'></div>
    <div class='item'></div>
</section>

再看一下主要的LESS设置:

.container{
    dispaly: flex;
    flex-basis: 600px;
    height: 100px;
    .item{
        &:nth-child(1){
            flex-shrink: 1;
            flex-basis: 200px;
        }
        &:nth-child(2){
            flex-shrink: 3;
            flex-basis: 600px;
        }
        &:nth-child(3){
            flex-shrink: 1;
            flex-basis: 400px;
        }
    }
}

最终效果图如下:

不带有padding与margin的flex-box

根据已有的结论,与效果图,我们给出前人的计算公式思想:

  • W_{flex}i :容器中第i个项目经过flex伸缩变化之后最终的宽度
  • S_i : 容器中第i个项目所占有的伸缩系数flex-shrink
  • W_{container} :容器的宽度
  • W_{width}i : 容器中第i个项目的宽度,即项目的属性width | flex-basis的值
  • H_{i} : 容器中第i个项目所占有的权重,即 W_{width} \times S_i
  • \sum_i^n(exp) : 求和函数,求in的表达式的和

有以上定义之后,即可得出以下定义:

  • W_{overflow} : 溢出宽度,即: (\sum_i^n W_{width}i) -W_{container}
  • H_{total} :项目总权重,即: \sum_i^n H_i

最终得到的公式为:

W_{flex}i = W_{width}i - W_{overflow} \times \frac {H_i}{H_{total}}

将变量带入计算公式可算出3个盒子的宽度:

W_{overflow} = (200 + 600 + 400) - 600 = 600px \\
H_{total} = (200\times1)+(600\times3)+(400\times1) = 2400 \\
W_{flex}1 = W_{width}1 - W_{overflow} \times \frac {H_1}{H_{total}} =200 -600 \times \frac{200\times1}{2400} = 150px \\
W_{flex}2 = W_{width}2 - W_{overflow} \times \frac {H_2}{H_{total}} = 600 - 600 \times \frac{600 \times 3}{ 2400 } = 150px \\
W_{flex}3  = W_{width}3 - W_{overflow} \times \frac {H_3}{H_{total}}= 400 - 600 \times \frac{400 \times 1}{ 2400 } = 300px

paddingmargin

让我们稍微改动一下LESS,查看存在 padding 时的盒子的宽度:

.container.padding{
    .item{
        padding: 100px;
        &:nth-child(1){
            flex-shrink: 1;
            flex-basis: 200px;
        }
        &:nth-child(2){
            flex-shrink: 5;
            flex-basis: 800px;
        }
        &:nth-child(3){
            flex-shrink: 1;
            flex-basis: 400px
        }
    }
}

最终的效果图如下:

存在 padding 的 flex-shrink 盒子

仔细观察可以发现,加了 padding 之后, padding 值并未受到影响,减小的仅仅是盒子的真实宽度,如图第二个div的宽度完全由 padding 组成:

加了padding后, padding并未受到影响

由此特性,我们更新一下定义:

  • W_{padding+border+margin}i : 容器中第i个项目的各个外边距宽度和, 由padding,border,margin属性值的加和
  • W_{box}i : 容器中第i个项目的盒子宽度(W_{box}i = W_{width}i + W_{padding+border+margin}i),由(width | flex-basis)属性值加上所有边距组成
  • W_{overflow} : 溢出宽度,即: (\sum_i^n W_{box}i) -W_{container}

于是有新公式:

W_{flex}i = W_{box}i - W_{overflow} \times \frac {H_i}{H_{total}}

我们将所有变量带入计算可有:

W_{overflow} = ((100+50*2)+(700+50*2)+(300+50*2)) - 600 = 800\\
H_{total} = 100\times1 + 700\times5 + 300\times1 =3900 \\
W_{flex}1 = W_{box}1 - W_{overflow} \times \frac {H_1}{H_{total}}  = 200 - 800 \times\frac{100 \times 1}{3900}= 179.4871794871794\\
W_{flex}2 = W_{box}2 - W_{overflow} \times \frac {H_2}{H_{total}}  = 200 - 800\times\frac{700 \times 5}{3900} = 82.0512820512821

诶,等一下,好像有哪里不对???和显示的数值不太一样。第一个盒子,明明是175px,第二个盒子,明明是100px,现在需要讲讲下一个特性

min-width 对 flex-shrink 的影响

在写文档之前曾有过这样的一段LESS设置:

.container.min-width{
    .item{
        flex-basis: 600px;
        &:nth-child(1){
            flex-shrink: 1;
        }
        &:nth-child(2){
            flex-shrink: 3;
            min-width: 150px;
        }
        &:nth-child(3){
            flex-shrink: 2;
        }
    }
}

得到的效果图如下:

flex-shrink 盒子设置 min-width

此时第一个盒子可以推算出被减去的250是这么来的: 250 = (600\times2+150-600) \times\frac{600\times1}{600\times1 + 600\times2 }

大胆推测:min-width被触发的时候,被触发的项目的flex-shrink的值应该为0,计算项目的总宽度时,触发min-width的项目宽度值,取为min-width属性值

我们可以尝试上面得到的结论去计算刚刚的padding,由于padding不会受到影响,所以盒子的最小宽度只能是100px,而那时候min-width被触发了,此时min-width0

W_{oveflow} = ((100+50 \times 2)+(0+ 50 \times2) + (300+(50 \times 2)) - 600 = 100 \\
H_{total} = 100 \times 1 + 700 \times 0 + 300 \times 1 = 400\\
W_{flex}1 = W_{box}1 - W_{overflow} \times \frac {H_1}{H_{total}}  = 200 - 100 \times\frac{100 \times 1}{400} = 175px \\
W_{flex}3 = W_{box}3 - W_{overflow} \times \frac {H_3}{H_{total}}  = 400 - 100 \times\frac{300 \times 1}{400} = 325px

计算公式终于正确了!

其实我们对之前错误的计算方式这样看(即,flex-shrink形变后的项目的宽度由各个边距的宽度加上项目形变后的真实宽度):

W_{flex}i = W_{padding+border+margin}i + (W_{width}i - W_{overflow} \times\frac{H_i}{H_{total}} )

这样我们在计算第二个项目时就能发现问题所在(注意,当时计算的溢出宽度是800px):

W_{flex}2 = W_{padding+border+margin}2 + (W_{width}2 - W_{overflow} \times\frac{H_2}{H_{total}} ) \\= (50\times2) + (700 - 800\times\frac{700\times5}{3900}) = 100 + (-17.9487179487179)\\

项目的真实宽度width属性值在经过形变之后居然变成了负值,这显然是不正确的。所以可推导出flex-shrink的变化,最多只能够使得项目的width的值与min-width一致。

结论

综上所述,我们最后可以来一个终极公式了:

  • W_{flex}i :容器中第i个项目经过flex伸缩变化之后最终的宽度
  • S_i : 容器中第i个项目所占有的伸缩系数flex-shrink
  • W_{container} :容器的宽度
  • W_{width}i : 容器中第i个项目的宽度,即项目的属性width | flex-basis的值
  • H_{i} : 容器中第i个项目所占有的权重,即 W_{width} \times S_i
  • W_{padding+border+margin}i : 容器中第i个项目的各个外边距宽度和, 由padding,border,margin属性值的加和
  • W_{box}i : 容器中第i个项目的盒子宽度(W_{box}i = W_{width}i + W_{padding+border+margin}i),由(width | flex-basis)属性值加上所有边距组成
  • \sum_i^n(exp) : 求和函数,求in的表达式的和
  • W_{overflow} : 溢出宽度,即: (\sum_i^n W_{box}i) -W_{container}
  • H_{total} :项目总权重,即: \sum_i^n H_i
  • \alpha : 描述min-width被触发的特殊情况,当flex-shrink形变后的某一项计算得到的width === min-width时,该项的flex-shrink变成0W_{width}i也变成min-width,每一个项目将重新计算

有公式:

W_{flex}i = W_{padding+border+margin}i + \alpha(W_{width}i - W_{overflow} \times\frac{H_i}{H_{total}})

结语

啰里啰唆讲了这么多,希望有看客能理解其flex-shrink的计算方式了。如有不合理之处还望指正。flex-grow的计算与flex-shrink不一致,但相对简单,不再叙述,其特殊的地方应该在max-width值的设置处。

参考