关于flex-grow,flex-shrink的计算

在介绍flex-grow,flex-shrink的计算之前,先明确两个概念:

  • 元素的实际宽度 = 元素的左边框+元素的左内边距+元素的内容宽度+元素的右内边距+元素的右边框;
  • 元素的总占宽度 = 元素的左外边距+元素的左边框+元素的左内边距+元素的内容宽度+元素的右内边距+元素的右边框+元素的右外边距;

同时也可以先思考一下元素的padding、border、margin、box-sizing、min-width、max-width 等属性对计算结果是否会产生影响。flex-grow的计算相比flex-shrink的计算来说更简单一些,因此首先介绍一下flex-grow的计算。

一、flex-grow的计算

flex-grow属性用于设置或检索弹性盒子的扩展比率。其用来设置当父元素的宽度大于所有子元素的宽度的和时(即父元素会有剩余空间),子元素如何分配父元素的剩余空间。 flex-grow的默认值为0,意思是该元素不索取父元素的剩余空间,如果值大于0,表示索取。值越大,索取的越厉害。计算规则可举例说明:假设存在宽为600px的父元素,其有三个子元素分别为:宽100px的first-child、宽80px的mid-child、宽为60px的last-child,则当前父元素的剩余宽为:600-100-80-60 = 360px,此时可能产生的计算结果为:

(1)三个子元素的flex-grow均为0,即都不索取父元素剩余宽,此时子元素宽度不变,父元素仍保留剩余宽;

(2)first-child、mid-child的flex-grow为1,last-child为的flex-grow为2,即first-child、mid-child、last-child均索取父元素剩余宽,则三个子元素的flex-grow和将父元素剩余宽分为了4份,占比为1:1:2,则最终:

first-child宽 = 自身宽100 + 父元素剩余宽占比1/4 * 父元素剩余宽360 = 100+1/4*360 = 190px;

mid-child宽 = 自身宽80 + 父元素剩余宽占比1/4 * 父元素剩余宽360 = 80+1/4*360 = 170px;

last-child宽 = 自身宽60 + 父元素剩余宽占比2/4 * 父元素剩余宽360 = 60+2/4*360 = 240px;

(3)其他情况即存在flex-grow为0的子元素也存在flex-grow不为0的子元素时,则为0的子元素宽度不变,不为0的子元素按照结果(2)中的计算方式索取父元素的剩余宽。

上面的举例是在padding、border、margin均为0,box-sizing为content-box,不设置max-width情况下flex-grow的计算,索取父元素的高时计算规则相同。

下面通过实例来演示flex-grow的计算规则,包括元素的padding、border、margin、box-sizing、max-width 等属性对计算结果产生的影响。

<!-- HTML -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>flex</title>
</head>
<body>
     <div class="grow-container">
        <div class="grow-first-child"></div>
        <div class="grow-mid-child"></div>
        <div class="grow-last-child"></div>
    </div>
</body>
</html>

上面是html代码,首先看情况1:padding、border、margin均为0,box-sizing为content-box,不设置max-width情况下flex-grow的计算,下面为css代码:

<style>
.grow-container {
    display: flex;
    background-color: beige;
    width: 600px;
    height: 100px;
}

.grow-container div {
     text-align: center;
     color: #FFFFFF;
     height: 40px;
}
        
.grow-first-child {
    width: 100px;
    flex-grow: 1;
    background-color: hotpink;
}
        
.grow-mid-child {
    width: 80px;
    flex-grow: 1;
    background-color: burlywood;
}
        
.grow-last-child {
    width: 60px;
    flex-grow: 2;
    background-color: powderblue;
}
</style>

结果如下图所示:(情况1的结果与上面的举例说明中的结果(2)展示的结果相同)

再来看情况2:设置父元素和子元素的padding、margin、border均设置10px,mid-child设置max-width属性值为100px,css代码如下:

    <style>
        .grow-container {
            display: flex;
            background-color: beige;
            width: 600px;
            height: 120px;
            padding: 10px;
            margin: 10px;
            border: 10px solid #FFddaa;
        }
        
        .grow-container div {
            text-align: center;
            color: #FFFFFF;
            height: 40px;
            padding: 10px;
            margin: 10px;
            border-style: solid;
            border-width: 10px;
        }
        
        .grow-first-child {
            width: 100px;
            flex-grow: 1;
            background-color: hotpink;
            border-color: deeppink;
        }
        
        .grow-mid-child {
            width: 80px;
            max-width: 100px;
            flex-grow: 1;
            background-color: burlywood;
            border-color: orange;
        }
        
        .grow-last-child {
            width: 60px;
            flex-grow: 2;
            background-color: powderblue;
            border-color: royalblue;
        }
    </style>

结果如下图:(其中总占宽为文章开头介绍的总占宽概念)

按照正常逻辑计算,三个子元素的总占宽最终应该与情况1中的子元素最终宽度相同,但由结果图可以看出并不如预料所示,因为子元素设置了padding、border、margin后,子元素的总占宽改变了,同时父元素的剩余宽也发生了改变,此时父元素的剩余宽为:

此时父元素剩余宽 = 父元素自身内容宽600 - first-child的总占宽(内容宽100+内边距20+边框20+外边距20) - mid-child的总占宽(内容宽80+内边距20+边框20+外边距20) - last-child的总占宽(内容宽60+内边距20+边框20+外边距20) = 600-160-140-120 = 180px;

且mid-child设置了最大宽度,因此mid-child在索取父元素的剩余宽时,最多只能索取可满足自身最大宽度限制的量,即mid-child索取了父元素剩余宽中的100-80 = 20px,则此时父元素剩余宽为180-20 = 160px,这时的剩余宽将由first-child和last-child联合索取,first-child和last-child的flex-grow值总和将此时的父元素剩余宽分为了1+2=3份,占比为1:2,因此最终

first-child的总占宽 =(自身宽100+内边距20+边框20+外边距20)+ first-child对父元素剩余宽的占比1/3 * 此时的父元素剩余宽160 = 160+1/3*160 = 160+53.33 = 213.33px;

mid-child的总占宽 = 自身最大宽100 + 内边距20 + 边框20 + 外边距20 = 160px;

last-child的总占宽 =(自身宽60+内边距20+边框20+外边距20)+ last-child对父元素剩余宽的占比2/3 * 此时的父元素剩余宽160 = 120+2/3*160 = 120+106.67 = 226.67px;

则三个子元素各自的内容宽度 = 元素各自的总占宽 - 各自的内边距 - 各自的边框 - 各自的外边距;

最后看情况3,在情况2的基础上设置父元素的box-sizing属性值为border-box,即父元素的样式为

        .grow-container {
            display: flex;
            background-color: beige;
            width: 600px;
            height: 180px;
            padding: 10px;
            margin: 10px;
            border: 10px solid #FFddaa;
            box-sizing: border-box;
        }

结果如下图所示:

父元素box-sizing设置为border-box后,父元素的内容宽度变为了560px,此时子元素可索取的宽度是父元素内容宽度的剩余宽,即在情况2的基础上父元素的剩余宽再次减少了40px,此时父元素的剩余宽为:180 - 40 = 140px,mid-child依然被最大宽所限制,则可被first-child和last-child索取的父元素剩余宽为:140 - 20 = 120px,因此各子元素最终总占宽为:

first-child的总占宽 =(自身宽100+内边距20+边框20+外边距20)+ first-child对父元素剩余宽的占比1/3 * 此时的父元素剩余宽120 = 160+1/3*120 = 200px;

child-child的总占宽 = 自身宽最大宽100+内边距20+边框20+外边距20 = 160px;

last-child的总占宽 =(自身宽60+内边距20+边框20+外边距20)+ last-child对父元素剩余宽的占比2/3 * 此时的父元素剩余宽120 = 120+2/3*120 = 200px;

三个子元素各自的内容宽度 = 元素各自的总占宽 - 各自的内边距 - 各自的边框 - 各自的外边距;

其他情况均可由上述三种情况加以分析,而在子元素扩展后仍达不到最大宽度情况下,则依旧按照flex-grow的占比进行计算。

综上所述,flex-grow的计算时需要关注的是:

(1)计算父元素剩余空间时使用的是 父元素的内容宽度(或高度);

(2)计算父元素剩余空间时要考虑子元素的边距和边框,即使用子元素的总占宽高来计算;

(3)子元素边距或边框不为0时,索取父元素的剩余空间后改变的是子元素的内容宽高,子元素的边距和边框是不会发生变化的;

(4)子元素若设置了max-width或者max-height,则需分析其最大能够索取的父元素剩余空间;

(4)若子元素没有设置宽高,则根据其内容确定其宽高。

可将flex-grow的计算用如下公式概括,以行主轴为例:

子元素的最终宽 = 子元素的自身宽 + 子元素的flex-grow / 全部子元素的flex-grow值之和 * 父元素的剩余宽度;

二、flex-shrink的计算

flex-shrink 属性指定了 flex 元素的收缩规则。即当父元素的宽度小于所有子元素的宽度的和时(即子元素会超出父元素),子元素如何缩小自己的宽度的。flex 元素仅在默认宽度之和大于容器的时候才会发生收缩,其收缩的大小是依据 flex-shrink 的值。当父元素的宽度小于所有子元素的宽度的和时,子元素的宽度会减小。值越大,减小的越厉害。如果值为0,表示不减小。flex-shrink的默认值为1,如果没有显示定义该属性,将会自动按照默认值1在所有因子相加之后计算比率来进行空间收缩。

介绍flex-shrink的计算之前先介绍一下影响flex-shrink计算的一个属性:flex-basis。flex-basis属性用来设置元素的初始宽度,width也可以设置宽度。如果元素上同时设置了width和flex-basis,那么width 的值就会被flex-basis覆盖掉。当没有显示设置flex-basis时,其默认值为auto,即元素自身的宽度。

首先也是举例说明运算规则:父元素宽600px,有两个子元素:first-child,last-child。first-child宽为500px,last-child宽为400px,。 则两个子元素总共超出父元素的宽度为(500+400)- 600 = 300px。 此时依然会有三种可能的计算结果:

(1)first-child和last-child都设置了flex-shrink为0,即都不会减小宽度,则此时子元素宽度保持不变,共超出父元素300px。

(2)first-child设置了flex-shrink为0,而last-child设置了flex-shrink为1,则first-child宽度会保持不变,last-child会减小宽度,其最终宽度为:自身宽度400 - 超出父元素的宽度300 = 100px。有意思的是此种情况下,若其中一个子元素的宽度超过或等于父元素且该子元素flex-shrink为0,则另一个flex-shrink不为0的子元素的宽则不再按照收缩规律计算,其宽为该子元素自身的自适应内容宽度,无内容即为0,当然一般情况下也不会如此去设计页面布局。

(3)first-child设置了flex-shrink为1,和last-child设置了flex-shrink为2,即first-child和last-child均会减小宽度,且将要超出父元素的宽度分为了1+2=3份,总权重为:first-child的宽500 * first-child的flex-shrink值1 + last-child的宽400 * last-child的flex-shrink值2 = 1300px。则first-child需要减小的宽度为:超出父元素的宽度300 * (first-child的宽500 * first-child的flex-shrink值1 / 总权重1300)= 115.39px,则first-child的最终宽度 = 自身宽500 - 需要减小的宽度115.39 = 384.61px。同理,last-child需要减小的宽度为:超出父元素的宽度300 * (last-child的宽400 * last-child的flex-shrink值2 / 总权重1300)= 184.61px,则last-child的最终宽度 = 自身宽400 - 需要减小的宽度184.61 = 215.39px。

上面的举例同样是在padding、border、margin均为0,box-sizing为content-box,且不设置min-width情况下flex-shrink的计算,收缩父元素的高时计算规则相同。

目前依旧结果(3)的演算过程可以概括出一个flex-shrink的计算公式:

子元素的最终宽度 = 子元素宽之和超出父元素的宽度 * ( 元素自身宽度flex-basis值[此处并不十分准确,下面实例会进一步说明] * 元素的flex-shrink值 / 总权重);

总权重 = 各个 子元素的自身宽度  与 子元素的flex-shrink值 的乘积之和(例如 上面例子中总权重的计算规则);

注:由上面介绍的元素属性flex-basis可知,元素自身宽度即 flex-basis 属性的值。

下面依旧使用实例说明flex-shrink的计算规则,包括padding、border、margin、box-sizing、min-width对flex-shrink计算的影响。

<!-- HTML -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>flex</title>
</head>
<body>
    <div class="shrink-container">
        <div class="first-child">first-child</div>
        <div class="last-child">last-child</div>
    </div>
</body>
</html>   

上面是html代码,依然先看情况1:padding、border、margin均为0,box-sizing为content-box,不设置min-width情况下flex-shrink的计算,下面为css代码:

<style>
        .shrink-container {
            display: flex;
            width: 600px;
            height: 300px;
            background-color: beige;
        }
        
        .shrink-container div {
            text-align: center;
            color: #FFFFFF;
            height: 80px;
        }
        
        .first-child {
            width: 500px;
            flex-shrink: 1;
            background-color: hotpink;
        }
        
        .last-child {
            width: 400px;
            flex-shrink: 2;
            height: 100px;
            background-color: powderblue;
        }
</style>

结果如下图所示:(即上面举例中可能的结果(3)的展示)

再来看情况2:设置父元素的box-sizing属性值为border-box,设置父元素和子元素的padding、margin、border均设置10px,,css代码如下:

<style>
        .shrink-container {
            display: flex;
            width: 600px;
            height: 300px;
            background-color: beige;
            border: 10px solid #FFddaa;
            padding: 10px;
            margin: 10px;
            box-sizing: border-box;
        }
        
        .shrink-container div {
            text-align: center;
            color: #FFFFFF;
            height: 80px;
            padding: 10px;
            margin: 10px;
            border-style: solid;
            border-width: 10px;
        }
        
        .first-child {
            width: 500px;
            flex-shrink: 1;
            background-color: hotpink;
            border-color: deeppink;
        }
        
        .last-child {
            width: 400px;
            flex-shrink: 2;
            height: 100px;
            background-color: powderblue;
            border-color: royalblue;
        }
</style>

结果如下图所示:

如若按照情况1中总结出的公式对情况2进行计算,会发现计算结果与上图中的结果并不相同,因此padding、margin、border、box-sizing等因素对flex-shrink计算的结果产生了影响,此时的一个计算过程为:

1. 首先与flex-grow相同,当父元素的box-sizing属性设置为border-box时,父元素所能容纳的子元素的最大宽度为:当前父元素的宽600 - 边框20 - 内边距20 = 560px;

2. 子元素设置了内外边距和边框,因此其总占宽也发生了变化,正常不收缩时,first-child的总占宽为: 内容宽500+内边距20+边框20+外边距20 = 560px,last-child的总占宽为:内容宽400+内边距20+边框20+外边距20 = 460px;

3. 综合前两步可得出当前子元素宽之和超过父元素内容宽为:560+460-560 = 460px;

4. 然后接下来需要算的就是目前的总权重了,按照情况1中flex-basis去计算总权重,可得出总权重 = first-child的宽500 * first-child的flex-shrink值1 + last-child的宽400 * last-child的flex-shrink值2 = 1300;注意:此处与flex-grow不同的是,flex-grow的计算一直使用的是元素的总占宽,但对于flex-shrink的计算却是在计算父元素的溢出宽时使用元素的总占宽,而在计算总权重时使用了元素的内容宽。

5. 最后可以计算子元素的宽了。

先计算first-child需要减少的宽度:子元素宽之和超过父元素内容宽460 * (first-child的内容宽500 *  first-child的flex-shrink值1 / 总权重1300)= 176.92px,则最终first-child的总占宽为:first-child的原始总占宽560 - first-child需要减少的宽度176.92 = 383.08px;

再计算last-child需要减少的宽度:子元素宽之和超过父元素内容宽460 * (last-child的内容宽400 * last-child的flex-shrink值2 / 总权重1300)= 283.08px,则最终last-child的总占宽为:last-child的原始总占宽460 - last-child需要减少的宽度283.08 = 176.92px;

第二种情况中提到了计算总权重时使用的是子元素的内容宽,下面就再次验证一下这个问题,即第三种情况,将子元素的box-sizing属性也设置为border-box,此时css代码的改动部分为:

       .shrink-container div {
            text-align: center;
            color: #FFFFFF;
            height: 80px;
            padding: 10px;
            margin: 10px;
            border-style: solid;
            border-width: 10px;
            box-sizing: border-box;
        }

这时子元素宽的情况为:first-child的flex-basis = 500px,而内容宽 = 500 - 内边距20 - 边框20 = 460px;last-child的flex-basis = 400px,内容宽 = 400 - 内边距20 - 边框20 = 360px,若按照情况1中的公式计算总权重时仍按照flex-basis去计算,则预期结果应该与情况2的结果是相同的,但真实的结果却并不是,真实结果如下图所示:

其计算过程如下:

1. 首先情况2相同,父元素所能容纳的子元素的最大宽度为: 560px;

2. 子元素设置了内外边距和边框,但因为设置了box-sizing为border-box,因此其元素宽 = 元素内容+内边距+边框,所以此时first-child总占宽为:宽500+外边距20 = 520px,last-child总占宽为:宽400+外边距20 = 420px;

3. 综合前两步可得出当前子元素宽之和超过父元素内容宽为:520+420-560 = 380px;

4. 然后接下来依旧算总权重,依照上面分析和得出的子元素内容宽可计算出总权重 = first-child的内容宽460 * first-child的flex-shrink值1 + last-child的内容宽360 * last-child的flex-shrink值2 = 1180;此处真实计算总权重时需要使用子元素内容宽。

5. 最后计算子元素的宽:

先计算first-child需要减少的宽度:子元素宽之和超过父元素内容宽380 * (first-child的内容宽460 *  first-child的flex-shrink值1 / 总权重1180)= 148.14px,则最终first-child的总占宽为:first-child的原始总占宽520 - first-child需要减少的宽度148.14 = 371.86px;

再计算last-child需要减少的宽度:子元素宽之和超过父元素内容宽380 * (last-child的内容宽360 * last-child的flex-shrink值2 / 总权重1180)= 231.86px,则最终last-child的总占宽为:last-child的原始总占宽420 - last-child需要减少的宽度231.86 = 188.14px;

可看出计算结果与上面的结果图是相同的。

最后还有一个影响因素min-width,这个与max-width对flex-grow的影响类似,大致可分为以下几种情况:

(1)当设置一个子元素的最小宽之后,若这个最小宽的值大于按照收缩因子计算后该子元素的宽度时,则产生的结果为该子元素的最终宽为其设定的最小宽,而另一个子元素直接填充父元素的剩余宽;

(2)当设置一个子元素的最小宽之后,若这个最小宽的值小于按照收缩因子计算后该子元素的宽度时,则产生的结果为按照正常收缩因子去计算的结果;

(3)若一个子元素的最小宽已经超过了父元素宽度,则另一个元素的宽为其内容的宽,无内容即为0。

对于多个子元素的情况可进行同样的分析。

综上所述,flex-shrink的计算规则可定义如下:(同样以横向主轴为例)

1. 父元素的可填充子元素的宽为父元素内容的宽;

2. 计算子元素宽之和溢出父元素的宽度:overflow_father_width = 子元素总占宽之和 - 父元素的内容宽度;

3. 计算子元素的总权重 = 每个子元素的内容宽 * 该子元素的flex-shrink值  结果相加之和;

4. 计算子元素的收缩占比:child_shrink_ratio = 该子元素的内容宽 * 该子元素的flex-shrink值 / 总权重;

5. 计算子元素需要减少的宽:child_need_to_shrink_width = overflow_father_width * child_shrink_ratio ;

6. 计算子元素的最终总占宽 = 子元素的原始总占宽 - child_need_to_shrink_width ;

注:子元素的宽、内容宽等可根据padding、margin、border、box-sizing等属性去计算得出。

注意:无论是flex-grow还是flex-shrink的计算,都是以flex-basis为基准的,当flex-basis为0%时,表明元素为零尺寸,不占父元素空间,所以即使其设置了明确的width,计算子元素宽之和时,也不会将其计算在内。即:若剩余子元素宽之和小于父元素宽度,则flex-basis为0%的元素按照宽为0处理,全部子元素按照上面的flex-grow正常计算;若剩余子元素宽之和大于父元素宽度,则flex-basis为0%的元素最终宽即为0(不考虑边距边框等其他属性的影响时),剩余元素按照比溢出父元素的宽度按照上面的flex-shrink规则进行计算。

参考:

1. http://www.bubuko.com/infodetail-2884898.html

2. https://www.cnblogs.com/liyan-web/p/11217330.html#FIXME

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值