你不知道的 flex-shrink 計算規(guī)則

對于 flex-shrink 我們都知道它在 flex 布局中控制 flex 盒子空間不足時子元素改如何收縮,平常開發(fā)中更多的是使用默認值 1 或者設置 0。
那設置其他值的時候會有什么效果呢,不少文章中描述都不是很細,在很長一段時間我甚至以為自己是了解它的。

開篇我們帶著幾個問題
1. “flex-shrink屬性定義了項目的縮小比例告抄,當父元素主軸方向空間不足的時候,子元素們按照 flex-shrink 的比例來縮小嵌牺〈蛲荩” 這句描述對嗎?
2. 一個父元素下有兩個子元素逆粹,兩個子元素各占用父元素 50% 且分別有 50px募疮、20px 的 padding。這個很簡單的需求用 flex 布局如何實現(xiàn)僻弹?如果嘗試以后和你的想象不同阿浓,那為什么會這樣呢?
3. 當空間不足時蹋绽,各項目具體會縮小多少芭毙?子元素 ``flex-shrink不同時有何影響筋蓖?子元素寬度會對縮小有影響嗎?父子元素的 margin稿蹲、padding扭勉、border 會對結(jié)果有影響嗎鹊奖?box-sizing 的值會有影響嗎苛聘? 如果你對以上的問題不能清楚的回答,或者嘗試以后發(fā)現(xiàn)和自己想象的不同忠聚,那這篇文章對于你可能會有一些用设哗。

首先我們看第一個問題

1. “flex-shrink屬性定義了項目的縮小比例,當父元素主軸方向空間不足的時候两蟀,子元素們按照 flex-shrink 的比例來縮小网梢。” 這句描述對嗎赂毯?
這句話描述其實不準確战虏。
flex-shrink 決定了子元素縮小系數(shù),但在具體計算的時候党涕,其實它還受到了 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.

從中我們可以看到,真正使用的縮小系數(shù)其實是 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` 決定了子元素縮小系數(shù)手趣,那我們知道子元素需要的空間是 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

所以三個元素真正的系數(shù)分別是 600/1000 200/1000 200/1000吧凉。200 的總額得出 120 40 40√ぶ荆可以看到和實際情況相符阀捅。

按照這個公式可以滿足多數(shù)情況的使用,但其中還隱藏著其他規(guī)則针余。下面我們看第二個問題

2. 一個父元素下有兩個子元素饲鄙,兩個子元素各占用父元素 50% 且分別有 50px凄诞、20px 的 padding。這個很簡單的需求用 flex 布局如何實現(xiàn)忍级?如果嘗試以后和你的想象不同帆谍,那為什么會這樣呢?

該問題其實是我發(fā)現(xiàn)自己對 flex-shrink` 不夠了解轴咱,從而研究的原因汛蝙。` 這個問題看起來很簡單吧,估計多數(shù)人第一反應是這樣:

[
復制代碼

](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); "復制代碼")

看起來收縮朴肺、放大系數(shù)都相等窖剑,兩個元素應該父元素 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

那么我們要如何實現(xiàn)子元素含 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 的數(shù)值設置的 width 其實是 box-sizing 的默認值 box-sizing: content-box; 那么理所當然會想到修改參數(shù)挽牢,改為 box-sizing: border-box; 然后 flex-base: 100%;
如此一來谱煤,兩個元素都如 IE 盒模型一樣,寬度包含 border padding禽拔,而且收縮刘离、放大系數(shù)都一樣,是不是就可以實現(xiàn)需求了呢睹栖?答案還是否定的硫惕,不但實現(xiàn)不了需求,甚至會出現(xiàn)一時間難以理解的數(shù)值


image

上面提到子元素的 padding 等值也會算在不可伸縮長度里凍結(jié)掉野来。為什么這么說呢恼除,我們結(jié)合上圖的原因來做解說

這個值是怎么來的呢,其經(jīng)過了以下的步驟
1. 計算子元素 flex-base 所代表的實際值 => 400px
2. 那兩個就是 800px,父元素 400px豁辉,主軸長度不夠令野,flex-shrink 開始生效。但第二步卻不是 flex-shrink * flex-base 得出真正的比例系數(shù)徽级,我們需要先得到“真正的” 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. 計算比例系數(shù) 3601=360努咐,3001=300苦蒿。所有其比例系數(shù)分別是 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 所占的長度凍結(jié),因為這些不可分配
把 子元素的 margin袍冷、border、padding 所占的空間凍結(jié)奴璃,因為這部分不會參與伸縮
剩下的空間才會作為正瞒滴、負長度分配給子元素

所以我們得出更詳細的壓縮計算公式

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的幫助汽馋。``

轉(zhuǎn)自: https://www.cnblogs.com/liyan-web/p/11217330.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侮东,一起剝皮案震驚了整個濱河市圈盔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌悄雅,老刑警劉巖驱敲,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異宽闲,居然都是意外死亡众眨,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門容诬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娩梨,“玉大人,你說我怎么就攤上這事览徒”范ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵习蓬,是天一觀的道長纽什。 經(jīng)常有香客問我,道長躲叼,這世上最難降的妖魔是什么芦缰? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮枫慷,結(jié)果婚禮上让蕾,老公的妹妹穿的比我還像新娘。我一直安慰自己或听,他們只是感情好探孝,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著神帅,像睡著了一般再姑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上找御,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天元镀,我揣著相機與錄音,去河邊找鬼霎桅。 笑死栖疑,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的滔驶。 我是一名探鬼主播遇革,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了萝快?” 一聲冷哼從身側(cè)響起锻霎,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎揪漩,沒想到半個月后旋恼,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡奄容,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年冰更,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昂勒。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡蜀细,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出戈盈,到底是詐尸還是另有隱情奠衔,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布奕谭,位于F島的核電站涣觉,受9級特大地震影響痴荐,放射性物質(zhì)發(fā)生泄漏血柳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一生兆、第九天 我趴在偏房一處隱蔽的房頂上張望难捌。 院中可真熱鬧,春花似錦鸦难、人聲如沸根吁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽击敌。三九已至,卻和暖如春拴事,著一層夾襖步出監(jiān)牢的瞬間沃斤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工刃宵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留衡瓶,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓牲证,卻偏偏與公主長得像哮针,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345