I signed up to participate in the first challenge of the Golden Stone Project - share the 100,000 prize pool, this is my 6th article, click to view the details of the event
The current front-end is basically the world of flex layout. Its simple usage and powerful functions are a major killer in CSS. The reason why flex layout stands out from the crowd is inseparable from the mathematical principles behind it. Today, I will lead you to understand the two core properties of flex layout:
- flex-grow
- flex-shrink
Remarks: This article will use a lot of elementary school mathematics knowledge, and it will be accompanied by beautiful tables and illustrations to describe the calculation process. The recommended reading time is 15 minutes.
flex-grow
The role of this property is: when the parent element has remaining space in the main axis direction, how the child elements allocate the remaining space . Its value is a weight, the default is 0, and the remaining space will be allocated according to the weight of each child element. Assuming that the flex-grows of the three sub-elements are a, b, and c, respectively, if there is a remaining space K in the container, it will be allocated to the corresponding sub-elements according to the weight ratio, that is, increase a*K/(a+b+c)
, b*K/(a+b+c)
, respectively c*K/(a+b+c)
.
regular scene
For example: the width of the parent element is 500px, and the widths of the three child elements a, b, and c are 100px, 150px, and 100px, respectively, so the remaining space is 150px. If the flex-grow of the three elements is 1, 2, and 3, what is the final width of each element?
According to the above formula, since 1+2+3 = 6, the growth rate of each element is:
1 / 6
2 / 6
3 / 6
The space and final width that the three elements are divided into are:
- grow
150px * 1 / 6 = 25px
, final width100px + 25px = 125px
- grow
150px * 2 / 6 = 50px
, final width150px + 50px = 200px
- grow
150px * 3 / 6 = 75px
, final width100px + 75px = 175px
Recorded in the form as follows:
500px container | initial width | flex-grow value | growth rate | growing width | final width |
---|---|---|---|---|---|
a (purple) | 100px | 1 | 1 / 6 | 25px | 125px |
b (green) | 150px | 2 | 2 / 6 | 50px | 200px |
c (blue) | 100px | 3 | 3 / 6 | 75px | 175px |
The actual effect is as follows:
Scenes disturbed by max-width
注意:flex-grow 会受到 max-width 的影响。如果某个元素 grow 后的结果大于 max-width 指定的值,那么 max-width 的值将会优先使用,那么剩下的元素如何分配剩余空间呢?我们把中间绿色的元素设置 max-width: 150px
让其不超过 150px,请问左边紫色和右边蓝色的元素的宽度分别是多少?
你可能会认为,既然中间绿色元素为150px,那么左右两个元素按照 flex-grow 的比例瓜分剩余的 150px 即可 ,即紫色元素 100px + 150px / 4 = 187.5px
,蓝色元素 100px + 3 * 150px /4 = 362.5px
。
然而并非如此,正确的计算方式是依然先按照 1:2:3 进行分配,计算得到中间的绿色元素为 200px,但是由于 max-width 为 150px,多出了 50px,那么这 50px 被左右两边的紫色和蓝色元素按照 1:3 的比例瓜分掉,也就是紫色元素宽度为 125px + 50px/4 = 137.5px
,蓝色元素宽度为175px + 3*50px/4 = 212.5px
,如下图所示:
值为小数的场景
有一点需要注意:flex-grow 取值可以是小数,例如 0.1,当所有元素的 flex-grow 之和小于 1 的时候,剩余空间不会全部分配给各个元素。实际上用来分配的空间是 sum * 剩余空间
,但比例保持不变。还是上面一个例子,但是三个元素的 flex-grow 分别是 0.1,0.2,0.3,那么计算公式将变成下面这样:
150 * 0.1 / 1 = 15px
150 * 0.2 / 1 = 30px
150 * 0.3 / 1 = 45px
三个元素的最终宽度分别为:
100px + 15px = 115px
150px + 30px = 180px
100px + 45px = 145px
即还有 150px * 0.4 = 60px
没有分配给任何子元素,如下图所示:
如果这个时候,我给中间的元素设置 max-width 为 150px 话,会是什么样的效果呢?多出来的 30px 会被左右的元素瓜分掉吗?如果是,那么分别瓜分多少呢?
其实,这种情况下剩余空间并不会分配给其余的元素,而是累加到留白的区域,即 60px 剩余空间增大到 90px。
内容自适应的场景
你可能会问,如果我们事先并没有给 a、b、c 三个子元素设置宽度的话,如何计算剩余空间呢?答案是:会根据其内容占用的宽度来计算。例如我们把三个 div 的 width 属性去掉,在里面分别添加 5 个汉字、3 个汉字和 8 个汉字,然后打开控制台通过 computed 面板来查看默认宽度。
发现一个汉字占据16px,所以a、b、c三个子元素的宽度分别是 80px、48px 和 128px,假设如果我们把容器改为 flex 布局,同样按照 1:2:3 的比例分配剩余空间,那么计算结果如下:
初始宽度 | flex-grow值 | 被分配到的剩余空间 | 最终宽度 | |
---|---|---|---|---|
a(紫色) | 80px | 1 | 40.66666px | 120.66666px |
b(绿色) | 48px | 2 | 81.33333px | 129.33333px |
c(蓝色) | 128px | 3 | 122px | 250px |
经过实践验证,也确实是如此:
总结
对于使用 flex-grow 进行从左到右布局的场景,总结如下:
- 如果所有元素的 flex-grow 之和大于等于 1,则子元素的宽度之和等于父元素宽度
- 如果 flex-grow 之和小于 1,则子元素的宽度之和小于父元素宽度
- 子元素增长率的计算公式为:元素的 flex-grow 值除以各元素 flex-grow 值之和
- max-width 可能会对 flex-grow 产生影响,影响的宽度由剩余元素按照比例重新计算
flex-shrink
该属性与 flex-grow
的作用恰恰相反,flex-shrink
用于定义:父元素在主轴方向空间不足时,子元素们如何收缩。其默认值为 1,表示元素宽度变小的一个权重分量,但是每个元素具体收缩多少,不仅仅取决于这个权重,还取决于自身的宽度。
常规场景
举个例子:父元素 400px,三个子元素宽度分别为为 150px、200px、150px,flex-shrink 的值分别为 1,2,3。通过计算得知400px - (150px + 200px + 150px) = -100px
,说明容器空间不足,还缺少 100px
,所以只能让内部的三个元素分别收缩一定的量来挤一挤,那分别收缩多少呢?
第一直觉就是:按照 1:2:3 的比例分配不就好了,各元素分别收缩 100px /6
、2*100px / 6
和 3*100px / 6
即可。
这种思路在 flex-grow 场景下是可行的,但是在 flex-shrink 下则不然,因为需要把元素自身的宽度也考虑进去,公式为:
- 总收缩宽度 = 各元素的宽度 * flex-shrink 值之和
- 各元素收缩率 = 元素宽度 * flex-shrink / 总收缩宽度
因此上面的总收缩宽度为 1 * 150px + 2 * 200px + 3 * 150px = 1000px
,各元素的收缩率分别是:
1 * 150px / 1000px = 0.15
2 * 200px / 1000px = 0.4
3 * 150px / 1000px = 0.45
因此三个元素分别收缩长度和最终宽度分别是:
- 收缩
100px * 0.15 = 15px
,最终宽度150px - 15px = 135px
- 收缩
100px * 0.4 = 40px
,最终宽度200px - 40px = 160px
- 收缩
100px * 0.45 = 45px
,最终宽度150px - 45px = 105px
记录成表格如下:
400px 容器 | 初始宽度 | flex-shrink值 | 收缩率 | 需收缩的空间 | 最终宽度 |
---|---|---|---|---|---|
a(紫色) | 150px | 1 | 0.15 | 15px | 135px |
b(绿色) | 200px | 2 | 0.4 | 40px | 160px |
c(蓝色) | 150px | 3 | 0.45 | 45px | 105px |
图示如下:
被 min-width 干扰的场景
与 max-width 会影响 flex-grow 类似,flex-shrink 也会受到 min-width 的影响,例如我们对中间绿色元素添加 min-width: 180px
的属性,左右紫色和蓝色元素最终的宽度是多少呢?
既然紫色区域锁定了 140px,不参与宽度收缩,那是不是超出的 100px 只能由左右两个元素来按照比例进行分配了呢?有了 flex-grow 的参照,相信大家也知道这么算是不对的,应该还是先按照不考虑 min-width 属性时进行计算,然后再把 min-width 超出的部分让剩余的元素按照比例承担,由于紫色的元素超出了 5px,按照上面的计算公式:
- 剩余元素加权宽度之和:
200px * 2 + 150px * 3 = 850px
- 绿色元素缩短宽度
200px * 2 * 5px / 850px = 2.35px
,因此剩余160px - 2.35px = 157.65px
- 蓝色元素缩短
150px * 3 * 5px / 850px = 2.65px
,因此剩余105px - 2.65px = 102.35px
图示如下:
值为小数的场景
同样,当所有元素的 flex-shrink 之和小于 1 时,计算方式也会有所不同,因为此时并不会收缩所有的空间,而只会收缩 flex-shrink 之和相对于 1 的比例的空间。还是上面的例子,但是 flex-shrink 分别改为 0.1,0.2,0.3,于是总权重为 150px * 0.1 + 200px * 0.2 + 150px * 0.3 = 100
。
三个元素收缩总和并不是 150px,而是只会收缩 100px 的 0.1 + 0.2 + 0.3 = 60%
的空间,即 60px,因此每个元素收缩的空间为:
60px * 0.1 * 150 / 100 = 9px
60px * 0.2 * 200 / 100 = 24px
60px * 0.3 * 150 / 100 = 27px
三个元素的最终宽度分别为:
150px - 9px = 141px
200px - 24px = 176px
150px - 27px = 123px
如下图所示:
内容自适应的场景
如果元素的宽度未指定,也会按照里面内容的大小进行动态计算,例如分别给三个元素里面添加文本,每个汉字的宽度是 16px,那么每个元素内容的宽度分别是 240px、160px 和 80px,加权宽度之和为 1 * 240px + 2 * 160px + 3 * 80px = 800px
:
表格计算结果如下:
400px 容器 | 初始宽度 | flex-shrink值 | 收缩率 | 收缩宽度 | 最终宽度 |
---|---|---|---|---|---|
a(紫色) | 240px | 1 | 0.3 | 24px | 216px |
b(绿色) | 160px | 2 | 0.2 | 32px | 128px |
c(蓝色) | 80px | 3 | 0.3 | 24px | 56px |
实际验证也确实如此:
总结
对于使用 flex-shrink 进行从左到右布局的场景,总结如下:
- 如果所有元素的 flex-shrink 之和大于等于 1,则子元素的宽度和等于父元素宽度
- If the sum of flex-shrink is less than 1, the sum of the widths of the child elements is greater than the width of the parent element
- The calculation formula of the child element shrinkage rate is: the value of flex-shrink multiplied by the width of the element divided by the sum of the flex-shrink value of each element multiplied by the width of the element
- min-width may have an effect on flex-shrink, the affected width is recalculated proportionally by the remaining elements