你不知道的 flex-shrink 计算规则

对于 flex-shrink 我们都知道它在 flex 布局中控制 flex 盒子空间不足时子元素改如何收缩,平常开发中更多的是使用默认值 1 或者设置 0。
那设置其他值的时候会有什么效果呢,不少文章中描述都不是很细,在很长一段时间我甚至以为自己是了解它的。

开篇我们带着几个问题
1. “flex-shrink属性定义了项目的缩小比例,当父元素主轴方向空间不足的时候,子元素们按照 flex-shrink 的比例来缩小。” 这句描述对吗?
2. 一个父元素下有两个子元素,两个子元素各占用父元素 50% 且分别有 50px、20px 的 padding。这个很简单的需求用 flex 布局如何实现?如果尝试以后和你的想象不同,那为什么会这样呢?
3. 当空间不足时,各项目具体会缩小多少?子元素 ``flex-shrink不同时有何影响?子元素宽度会对缩小有影响吗?父子元素的 margin、padding、border 会对结果有影响吗?box-sizing 的值会有影响吗? 如果你对以上的问题不能清楚的回答,或者尝试以后发现和自己想象的不同,那这篇文章对于你可能会有一些用。

首先我们看第一个问题

1. “flex-shrink属性定义了项目的缩小比例,当父元素主轴方向空间不足的时候,子元素们按照 flex-shrink 的比例来缩小。” 这句描述对吗?
这句话描述其实不准确。
flex-shrink 决定了子元素缩小系数,但在具体计算的时候,其实它还受到了 flex base size 的影响。 w3c 对于的 flex-shrink 的描述有这样一段备注
Note: The flex shrink factor is multiplied by the flex base size when distributing negative space. This distributes negative space in proportion to how much the item is able to shrink, so that e.g. a small item won’t shrink to zero before a larger item has been noticeably reduced.

从中我们可以看到,真正使用的缩小系数其实是 flex shrink factor * flex base size。下面我们用一个例子来说明它

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"><style> .box { display: flex; width: 400px; outline: 1px red solid;

} .item1 { flex: 0 2 300px; background-color: #32d6d6;
} .item2 { flex: 0 1 200px; background-color: #e2a83e;
} .item3 { flex: 0 2 100px; background-color: #b85ad0;
}

</style> ... <div class="box">
<div class="item1">1</div>
<div class="item2">2</div>
<div class="item3">3</div>
</div> </pre>

[
复制代码

](javascript:void(0); "复制代码")

按照不准确的描述 flex-shrink` 决定了子元素缩小系数,那我们知道子元素需要的空间是 300+200+100 一共 600px,但父元素只有 400px 所以分别的负空间是 200px,或者说需要缩小 200px。三个元素flex-shrink` 分别为 2 1 2,表面上看应该分别缩小 80 40 80,那三个元素应该 220 160 20。但事实是这样吗?
如果你也尝试一下,就会知道,实际上的效果是 180 160 60。
我们来看一下正确的计算方式:

flex-shrink * flex-base(姑且先这么写,之后会修正) => factor
2 * 300 => 600
1 * 200 => 200
2 * 100 => 200

所以三个元素真正的系数分别是 600/1000 200/1000 200/1000。200 的总额得出 120 40 40。可以看到和实际情况相符。

按照这个公式可以满足多数情况的使用,但其中还隐藏着其他规则。下面我们看第二个问题

2. 一个父元素下有两个子元素,两个子元素各占用父元素 50% 且分别有 50px、20px 的 padding。这个很简单的需求用 flex 布局如何实现?如果尝试以后和你的想象不同,那为什么会这样呢?

该问题其实是我发现自己对 flex-shrink` 不够了解,从而研究的原因。` 这个问题看起来很简单吧,估计多数人第一反应是这样:

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"><style> .box { display: flex; width: 400px; outline: 1px red solid;
} .item-2-1 { flex: 1 1; padding: 50px; background-color: #32d6d6; background-clip: content-box;
} .item-2-2 { flex: 1 1; padding: 20px; background-color: #b85ad0; background-clip: content-box;
}
</style> ... <div class="box">
<div class="item-2-1">1</div>
<div class="item-2-2">2</div>
</div></pre>

[
复制代码

](javascript:void(0); "复制代码")

看起来收缩、放大系数都相等,两个元素应该父元素 400 像素,每个 200 对吧?我们看一下实际情况

image

是不是和想象不同?

下面说明原因
w3c 里对于元素可用长度有这样的描述

dimension of the flex container’s content box is a definite size, use that; if that dimension of the flex container is being sized under a min or max-content constraint, the available space in that dimension is that constraint; otherwise, subtract the flex container’s margin, border, and padding from the space available to the flex container in that dimension and use that value. This might result in an infinite value.

我们可用看到其实计算主轴可以用长度的计算是要去除 margin, border, and padding 的,但这里的描述我觉得其实也不准确,他这里说的只是 flex container,似乎只是父元素里的 margin、border、padding。但在我的实际测试的时候,其实还包括更多
比如:直接子元素的 margin、border、padding 甚至是直接子元素的 min-width,稍缓我会说为什么

那么我们来计算一下
400 - 502 - 202 = 260(可用空间)
flex-base 为 0,flex-grow 都为 1;260*(1/2)= 130
130 + 20 + 20 = 170

那么我们要如何实现子元素含 padding 时也平分空间呢?
flex-base 的描述里有这样的一句

As another corollary, flex-basis determines the size of the content box, unless otherwise specified such as by box-sizing [CSS3UI].

可以看到 flex-basis 的数值设置的 width 其实是 box-sizing 的默认值 box-sizing: content-box; 那么理所当然会想到修改参数,改为 box-sizing: border-box; 然后 flex-base: 100%;
如此一来,两个元素都如 IE 盒模型一样,宽度包含 border padding,而且收缩、放大系数都一样,是不是就可以实现需求了呢?答案还是否定的,不但实现不了需求,甚至会出现一时间难以理解的数值


image

上面提到子元素的 padding 等值也会算在不可伸缩长度里冻结掉。为什么这么说呢,我们结合上图的原因来做解说

这个值是怎么来的呢,其经过了以下的步骤
1. 计算子元素 flex-base 所代表的实际值 => 400px
2. 那两个就是 800px,父元素 400px,主轴长度不够,flex-shrink 开始生效。但第二步却不是 flex-shrink * flex-base 得出真正的比例系数,我们需要先得到“真正的” flex base size,其实之前我们提及过
 事实上,真正的 flex base size 并非单纯的是 flex-base。更准确的说,子元素 flex-base 设置后带来的 content width。比如这里 flex base size = box width 400 - padding 202 - border 0 = 360,以及两外一个 400-502=300。
3. 计算比例系数 3601=360,3001=300。所有其比例系数分别是 300/660、360/660。
4. 计算需要分配的负空间 4002-400 = 400px
5. 计算分别需要缩减的部分 400
(300/660)≈181.8181、400*(360/660)≈218.1818
6. 实际宽度 400-181.8181≈218.18 400-21≈181.81

对于其原因,w3c 里对于如何计算弹性长度有这样的一个描述
> Size inflexible items. Freeze, setting its target main size to its hypothetical main size…

对此我是这样理解的,在计算子元素主轴长度的时候,有这么一些操作
把 flex container 的 margin、border、padding 所占的长度冻结,因为这些不可分配
把 子元素的 margin、border、padding 所占的空间冻结,因为这部分不会参与伸缩
剩下的空间才会作为正、负长度分配给子元素

所以我们得出更详细的压缩计算公式

flex_container_available_length = flex_container_content_width(or height)
flex_items_length = flex_item_box_width + flex_item_box_width + flex_item_box_width...
shrink_factor = (flex-shrink * flex_base_size) /((flex-shrink * flex_base_size) + (flex-shrink * flex_base_size) + ...)
will_allocate_length = flex_container - flex_items_length
flex_item_width = flex_item_box_width + flex_item_box_width * (will_allocate_length * shrink_factor)

注1:flex_item_box_width = margin + padding + border + content width
注2: flex_base_size 取决于该元素的 box-sizing 和 flex-base,其值为 border-box 时,flex_base_size = flex-base - padding - barder;其值为 content-box 时,flex_base_size = flex-base。

为了验证公式的正确性,我们随意设计一个 margin padding border box-sizing flex-shrink flex-base 多样繁杂的 demo(.flexBox)

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>flex-shrink demo</title>
<style> .box { display: flex; width: 400px; outline: 1px red solid;
} .item1 { flex: 0 2 300px; background-color: #32d6d6;
} .item2 { flex: 0 1 200px; background-color: #e2a83e;
} .item3 { flex: 0 2 100px; background-color: #b85ad0;
} .item-2-1 { flex: 1 1; padding: 50px; background-color: #32d6d6; background-clip: content-box;
} .item-2-2 { flex: 1 1; padding: 20px; background-color: #b85ad0; background-clip: content-box;
} .demo-3 { flex: 1 1; display: flex; align-items: center;
} .demo-3-1 { flex: 1 1; padding: 50px; background-color: #32d6d6; background-clip: content-box;
} .demo-3-2 { flex: 1 1; padding: 20px; background-color: #b85ad0; background-clip: content-box;
} .item-border-box { flex-basis: 100%; box-sizing: border-box;
} .flexBox { display: flex; width: 1500px; outline: 1px red solid;
} .flexItem-1, .flexItem-2, .flexItem-3 { flex-shrink: 2; flex-basis: 300px;
} .flexItem-1 { margin: 0 10px;
} .flexItem-2 { padding: 0 20px; border: 5px #ccc solid; box-sizing: content-box;
} .flexItem-3 { padding: 0 20px; border: 5px #ccc solid; box-sizing: border-box;
} .flexItem-4, .flexItem-5, .flexItem-6 { flex-shrink: 1; flex-basis: 200px;
} .flexItem-4 { padding: 0 10px;
} .flexItem-5 { border: 5px #ccc solid; margin: 0 10px; box-sizing: content-box;
} .flexItem-6 { border: 5px #ccc solid; margin: 0 10px; box-sizing: border-box;
} .flexItem-7, .flexItem-8, .flexItem-9 { flex-shrink: 2; flex-basis: 100px;
} .flexItem-7 { border: 5px #ccc solid;
} .flexItem-8 { padding: 0 30px; margin: 0 10px; box-sizing: content-box;
} .flexItem-9 { padding: 0 30px; margin: 0 10px; box-sizing: border-box;
}
</style>
</head>

<body>
<div class="box">
<div class="item1">1</div>
<div class="item2">2</div>
<div class="item3">3</div>
</div>
<br />

<div class="box">
    <div class="item-2-1">1</div>
    <div class="item-2-2">2</div>
</div>
<br />

<div class="box">
    <div class="demo-3">
        <div class="demo-3-1"> 1 </div>
    </div>
    <div class="demo-3">
        <div class="demo-3-2"> 2 </div>
    </div>
</div>
<br />

<div class="box">
    <div class="item-2-1 item-border-box">1</div>
    <div class="item-2-2 item-border-box">2</div>
</div>
<br />

<div class="flexBox">
    <div class="flexItem-1" title="300 - (550*600/2770) ≈ 180.866">1</div>
    <div class="flexItem-2" title="300 - (550*600/2770) ≈ 180.866">2</div>
    <div class="flexItem-3" title="300 - (550*500/2770) ≈ 200.722 - 20*2 - 10*2 = 150.722">3</div>
    <div class="flexItem-4" title="200 - (550*200/2770) ≈ 160.288">4</div>
    <div class="flexItem-5" title="200 - (550*200/2770) ≈ 160.288">5</div>
    <div class="flexItem-6" title="200 - (550*190/2770) ≈ 162.274 - 5*2 = 152.274">6</div>
    <div class="flexItem-7" title="100 - (550*200/2770) ≈ 60.288">7</div>
    <div class="flexItem-8" title="100 - (550*200/2770) ≈ 60.288">8</div>
    <div class="flexItem-9" title="100 - (550*80/2770) ≈ 84.115 - 30*2 = 24.115">9</div>
</div>
<!-- flex_container_available_length = 1500
flex_items_length = (300+10*2) + (300+20*2+5*2) + (300) + (200+10*2) + (200+10*2+5*2) + (200+10*2) + (100+5*2) + (100+10*2+30*2) + (100+10*2)
                  = 2050
shrink_factor = 2770
    300*2  600
    300*2  600
    (300-20*2-5*2)*2  500

    200*1 200
    200*1 200
    (200-5*2)*1 190

    100*2  200
    100*2  200
    (100-30*2)*2  80

will_allocate_length = 1500 - 2050 = -550

flex_item_width
    300 - (550*600/2770) = 180.866
    300 - (550*600/2770) = 180.866
    300 - (550*500/2770) = 200.722 - 20*2 - 10*2 = 150.722

    200 - (550*200/2770) = 160.288
    200 - (550*200/2770) = 160.288
    200 - (550*190/2770) = 162.274 - 5*2 = 152.274

    100 - (550*200/2770) = 60.288
    100 - (550*200/2770) = 60.288
    100 - (550*80/2770) = 84.115 - 30*2 = 24.115 -->

</body>

</html></pre>

[
复制代码

](javascript:void(0); "复制代码")

上面的代码包括文章中所有的 demo 的代码,和第二个问题里说的需求的解决方法(其实巨简单)

``文章里留下的另一个坑,min-width 会对计算有什么影响呢?这个问题留给你自己尝试思考吧。如果想不通,也欢迎留言讨论。

最后,附上资料, 同时感谢stackoverflow的帮助。``

转自: https://www.cnblogs.com/liyan-web/p/11217330.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349