深入理解 Flex 佈局以及計算
起因
對於Flex佈局,閱讀了 大漠老師和其他老師寫的文章後,我還是不太理解Flexbox是如何彈性的計運算元級專案的大小以及一些其他細節。在大漠老師的幫助下,我去查閱Flexbox 的 W3C 規範文件。
注:本篇博文不適合未接觸過Flex 佈局的人, 如果想了解flex 佈局基礎。請參考理解Flexbox:你需要知道的一切
對於flex盒模型的設計期望
flex盒模型是被期望設計成:
- 在任何流動的方向上(包括上下左右)都能進行良好的佈局
- 可以以逆序 或者 以任意順序排列布局
- 可以線性的沿著主軸一字排開 或者 沿著側軸換行排列
- 可以彈性的在任意的容器中伸縮大小(今天重點研究的主題)
- 可以使子元素們在容器主軸方向上 或者 在容器側軸方向上 進行對齊
- 可以動態的 沿著主軸方向 伸縮子級的尺寸,與此同時保證父級側軸方向上的尺寸
主軸和側軸
很有必要先向大家解釋清楚 3個問題
- 什麼是主軸
- 什麼是側軸
- 他們是如何切換的
首先每一根軸都包括 三個東西:維度、方向、尺寸
什麼意思呢?
- 所謂的維度實際上就是意思就是子元素 橫著排還是豎著排(
x
軸 或y
軸) - 方向 即排列子元素的順序 順序還是逆序
- 尺寸 即
width
[height
] : 每一個子元素在主軸方向所佔的位置的總和 如果主軸是水平的,那麼尺寸就是父元素內所有item
的outerWidth
總和,如果主軸是垂直的,那麼尺寸就是父元素的outerHeight
主軸是依靠 flex-direction
和 所有子元素在主軸方向上的item-size
的總和確定的,flex-direction
這個屬性可以控制子元素的排列方向和排列順序。
側軸是依靠 flex-wrap
和 所有子元素在主軸方向上的item-size
的總和確定的,flex-wrap
可以控制子元素 在側軸方向上的排列方式以及順序。
而關於不同種類不同情況下的 item-size
我們會在下面討論,現在您可以簡單將它理解為width
[height
]。
盜規範中的一張圖
為了方便 flex-direction + flex-wrap
合併成了一個屬性 flex-flow
通過這個簡單而複雜的屬性,我們就能夠控制所有子元素的水平和垂直方向,逆序排列和順序,換行和不換行。
主側軸的切換十分簡單,當主軸設定的時候,它的垂直面,就預設被設定成了側軸。如:
flex-flow: row-reverse wrap-reverse;
這條CSS屬效能夠告訴我們那些資訊?
- 子元素是橫著排列的,主軸是水平的橫軸,側軸是豎直的縱軸
- 子元素是逆序並沿著主軸排列的,從右到左
- 子元素是換行的
- 子元素是逆序並沿著側軸排列的,從下到上
FFC (flex formatting context)
Flexbox 佈局新定義了格式化上下文,類似 BFC(block formatting context)。有多類似呢? 就是除了佈局和一些細節不同以外的一切規則都和 BFC 是相同的
。
注意 : 我所指的Flexbox 是指設定了
display: flex;
或display: inline-flex;
的盒子。不是指單單設定了display: flex;
的盒子。
例如,設定了 display: flex;
或 display: inline-flex
的元素,和BFC一樣,不會被浮動的元素遮蓋,不會垂直外邊距坍塌等等。
而對於設定了 display: inline-flex
的盒子來說,我們可以類比 display: inline-box;
行理解。即 一個被行列化後的 Flexbox。它不會獨佔一行,但是可以設定寬和高。
與BFC的細微區別
但需要注意的是以下幾點細節,Flexbox 佈局 和 Block 佈局是有細微區別的
- Flexbox 不支援
::first-line
和::first-letter
這兩種偽元素 vertical-align
對 Flexbox 中的子元素 是沒有效果的float
和clear
屬性對 Flexbox 中的子元素是沒有效果的,也不會使子元素脫離文件流(但是對Flexbox 是有效果的!)- 多欄佈局
(column-*)
在 Flexbox 中也是失效的,就是說我們不能使用多欄佈局在Flexbox 排列其下的子元素(魚和熊掌不可得兼嘛) - Flexbox 下的子元素不會繼承父級容器的寬
flex item(flex 子元素)
CSS解析器會把 定義了 display: flex;
和 display: inline-flex;
的 Flexbox 下的子元素外部裝進一個看不見的盒子裡,我們通過排列這些盒子來達到排序、佈局、 伸縮的目的。
規範中把這種盒子 稱為 flex item
,而子元素中包括了 標籤節點 以及 文字節點。標籤節點很容易理解,需要注意的是文字節點。
預設情況下,flex
會將 連續的文字節點 裝進 flex-item 之中,使文字可以和標籤節點一起排序和定位。
值得注意的是,空格也是文字節點,所以 white-space
會影響Flexbox 中的佈局:
設定了white-space: pre
的Flexbox
flex-item-size 如何計算的
item-size
(尺寸)為主軸方向上item
的 content
再加上自身的margin
、 border
和 padding
就是這個 item
的尺寸。
在規範中 介紹了 flex-item content 的計算方式
分為以下這幾種情況
1. flex-basis 的優先順序比 width[height]: 非auto; 高
如果子元素沒有內容和預設固定寬高,且設定了flex-basis
。flex-item content
以flex-basis
來決定,無論width[height]
設定了多少。
(可理解為 flex-basis
比 width[height]: 非auto;
的優先順序高)
flex-basis
的優先順序比width[height]
高,無論width[height]
設定多少,flex-item content都以flex-basis
來決定。
2.元素存在預設寬高
如果子元素有預設固定寬高(例如input
標籤)、並且設定了 flex-basis
,那麼它的 content
以 固定寬高為下限,如果flex-basis
超過了固定寬高,那麼flex-basis
則成為其 content
,如果flex-basis
比固定寬高小,那麼以固定寬高為 content
。
對於固定元素的尺寸設定
3.元素存在 min-width[height] 或者 max-width[height]
如果flex-item
有min-width[min-height]
的限制,那麼flex-item content
按照 min-width
值為下限,如果 flex-basis
的值大於 min-width[min-height]
那麼flex-item content
以 flex-basis
計算。
如果flex-basis
的值小於 min-width[min-height]
那麼flex-item content
以min-width[min-height]
計算:
如果 min-width[min-height]
的值已經超出了容器的尺寸,那麼即使設定了 flex-shrink
。 CSS解析器也不會進行將這個item
的 content shrink,而是堅持保留它的min-width[min-height]
:
如果Flexbox 設定的min-width
超出了flex container 的範圍, 不會對其進行壓縮。
反之,如果設定了 max-width[height]
的值,那麼設定flex-basis
無法超過這個值,對於flex-grow
也僅僅只會增長到 max-width[height]
這個上限。
在下面的章節,我們會仔細討論這種情況下,佈局的計算。
4.width[height]: auto; 優先順序等於 flex-basis。
前面提到,如果給item同時設定了width[height]
和 flex-basis
的話。flex-item content以flex-basis
來決定。但是實際上預設的 width[height]: auto;
優先順序是等於 flex-basis
的。
CSS解析器對比兩者的值,兩者誰大取誰 作為item的基本尺寸,如果一個item沒有內容,flex-item content就會以flex-basis
來決定。
但是如果item有了內容,且內容撐開的尺寸比flex-basis
大,那麼flex-item content就會以width[height]: auto;
來決定,且無法被 shrink。反之,如果比flex-basis
小,flex-item content就會以flex-basis
來決定:
width: auto;
內容長度比 flex-basis
大,則 flex-item content以內容長度來決定,且無法shrink
如果 flex-basis
的長度大於文字內容長度,那麼flex-item content以 flex-basis
來決定
同時設定了flex-basis: 800px;
和 width: 1px;
flex-item content以 flex-basis
來決定,可以發生shrink
注意2號盒子我設定了 flex-shrink: 1;
1號盒子和3號盒子我設定了 flex-shrink: 0;
意思就是我將所有的需要shrink的空間都壓到了2號盒子上,總共的需要 shrink的空間為 0 * 600 + 1 * 20 + 0 * 100 = -20
;而2號盒子只有20
的空間,理應被完全shrink變為0
,但是值得注意的是2號盒子並沒有被完全 shrink,還保留了一個文字的距離。
除此之外,overflow: hidden;
也會影響
overflow: hidden;
把文字長度限制在了600px
; 小於 flex-basis: 700px;
所以flex-item content以flex-basis
來決定,可以 shrink。
隱藏屬性對 items-size 的影響
我針對了 display: none; visibility: hidden; visibility: collapse; transform: scale;
等屬性對 items 進行測試。
結果如下:
- 如果設定了
visibility: hidden; | visibility: collapse; | transform: scale;
的flex-item content 依然被算進主軸尺寸,CSS 解析器依然會以他們flex-grow | flex-shrink
將可用空間 或者 負可用空間 分配給他們 - 如果設定了
display: none;
CSS解析器不會對該item的空間進行計算,也不會對其grow空間
關於position: absolute 對item影響
position: absolute
也是適用 Flexbox 中的子元素的,並且,設定了position: absolute
屬性的子元素,也會受到 Flexbox 排列的影響。
設定了absolute
的子元素重疊在了一起,但是依然會受到 align-items: center;
的影響而居中。對於 Flexbox 來說,設定了position: absolute;
並不會對其下的子元素產生任何影響。
我們重點看 Flexbox 下的子元素設定了absolute
後有什麼結果。
根據我做的實驗,我得到了如下的結論:
flexbox 下設定了absolute的子元素的位置受3個方面的影響:
- flexbox 流下面的
justify-content
和align-items
- 單個子元素的
top
、left
、right
、bottom
- 單個子元素的
margin
這裡我們不討論 translate
因為 translate
只是視覺上位置的改變:
設定了absolute
的item 不會影響佈局,如圖,其中1 2 3 4 5 號是設定了absolute
的item,而 6 7 8 9號是沒有設定absolute
的item Flexbox 我設定了 justify-content: center;
和 align-items: center;
每一個item我都給了 margin: 20px;
:
- 我們可以看到,由於
absolute
的原因, 12345號的item 不會影響 6789號的排布。結論:脫離了文件流的 item 不會影響 正常的flex 佈局。 - 如圖上 4號 item, 設定了
absolute
但是沒有設定top / left
這些值,位置居中偏下。結論:如果對子元素設定了position: absolute;
屬性而沒有設定 topleft
這些值。子元素受 Flexbox 的justify-content: center
、align-items: center
和margin
的影響 - 如圖上1235 號item, 我給他們分別設定
top
、left
、right
、bottom
等值。5號元素設定了margin-left: 50px;
和padding-bottom: -999999px;
結論:top
、left
、right
、bottom
等值會覆蓋justify-content: center;
和align-items: center;
設定的位置,使item 自由定位。margin
自始至終都會影響item的位置,而padding
不會(我試過padding
設500px
的情況,padding
會影響item的大小) - 如果對上圖 12345號item 不設定
top
、left
、right
、bottom
等值。對父級的justify-content
和align-items
設定center
以外的其他值的話:如果設定了flex-start
所有元素不分開,定位在 主軸起點;如果設定了flex-start
所有元素不分開,定位在 主軸終點;如果設定justify-content: space-around;
效果等同於center
,即所有的元素疊在一起居中,且items不會產生間隔;如果設定了justify-content: space-between;
效果等同於flex-start
, 且items不會分開;如果設定了align-items: flex-start;
所有元素不分開,定位在 側軸起點;如果設定了align-items: flex-end;
所有元素不分開,定位在 側軸終點;如果設定了align-items: stretch | baseline;
也是沒有任何效果, items 不會跟隨側軸拉伸 或是 根據baseline 對齊 - 如果對單個item 設定 align-self,除了 flex-start | flex-end | center 有效之外,其他都失效
通過上面一系列的測試我們可以清楚的認識到 justify-content
、align-items
和 top
、left
、right
、bottom
都是位置屬性,而且 top
、left
、right
、bottom
會覆蓋justify-content
和align-items
的值。(以上前提是一定要設定position: absolute
不然 top
、left
、right
、bottom
無效)。
而 margin
的優先順序是和 top
、left
、right
、bottom
一樣的,也就是說 margin
和 top
、left
、right
、bottom
所設定的值會同時生效。
優先順序排序為: margin = justify-content | align-items > top、left、right、bottom
。
flex-basis、flex-grow、flex-shrink 以及相應的計算
flex-basis
、flex-grow
、flex-shrink
是FFC下特有的屬性,只有父級元素設定了 display: flex | inline-flex;
才會生效,並且只針對主軸方向生效。
如果 主軸是水平的,即 flex-direction: row;
那麼 flex-basis
、flex-grow
、flex-shrink
控制的就是單個item的寬度。
如果 主軸是垂直的,即 flex-direction: column;
那麼 flex-basis
、flex-grow
、flex-shrink
控制的就是單個item的高度。而flex-grow
和 flex-shrink
是用於 主軸方向上對 (負)可用空間 進行伸縮的。
這要分兩種情況,換行或者不換行。
1.如果 flex-wrap: nowrap; 即不換行。
那麼所有items 都會在主軸方向上的一條線上排列,CSS解析器會計算 items 在主軸方向上所佔的空間 相對於 Flexbox 在主軸方向的所佔的空間進行比較計算。
如果 items 所佔的空間是小於Flexbox的 那麼說明Flexbox 還沒有填滿,CSS解析器就會計算還有多少空間沒有填滿,根據每一個item所設定的flex-grow
設定的值,將這些空間分配按比例分配給每一個item。
可用空間
如果 items 所佔的空間是大於Flexbox的 那麼說明Flexbox 被填滿了,CSS解析器就會計算超出了多少空間,根據每一個item所設定的flex-shrink
設定的值,將這些空間分配按比例縮小每一個item
超出的空間
那麼CSS解析器在這種情況下是怎樣計算的呢?上一章我們勞神費力理解的item-size終於派上用場了。flex-grow
計算流程是:
可用空間 = 將flexbox-content - 每個item-size的總和
將每一個item所設定的 grow 全部加起來,將可用空間除以grow,得到單位分配空間。
根據每一個item 設定的 grow 來算,如果一個item 的grow 為 2,那麼 這個 item 在主軸上的尺寸就需要延伸 2*單位分配空間的大小。
那麼 每一個 item 就需要在原基礎上 加上被分配的大小 就完成了grow:
分配前
分配後
簡單理解就是,將超出的部分,可能多,也可能少,根據 grow 來分配成 x
份,在根據每個 item 所設定的份數,將相差的部分分割給每一個item。
注意:
flex-shrink
的計算流程和flex-grow
的計算流程不同。
flex-shrink
計算流程是:先將所有專案 按照 flex-shrink * item-size
的方式加起來 得到一個加權和,然後計算出 每一份 item 的 shrink比例:
shrink比例 = flex-shrink * item-size / 之前的總和;
然後計算 子元素超出父級的部分(負可用空間),每一個item 減去這個 shrink比例 * 負可用空間即可
:
shrink前
shrink後
2. 如果flex-wrap: wrap[wrap-reverse]; 即換行
那麼items 都會先在主軸方向上的多條線上排列,CSS解析器先會計算 每一條線 在主軸方向上尺寸 相對於 Flexbox 容器的width[height]
進行比較計算,每條線之間互不干擾:
未分配之前
平均分配後
由於在一行內 如果item-size 累加超過了Flexbox 的尺寸就會另起一行進行排列,所以在這種情況下,不會存在 shrink 的情況,而只有 grow 的情況。
max-width[height] 情況下 flex-grow 的計算流程
由於可能存在某一個或多個item 設定了有max-width[height]
。所以,CSS引擎會先進行一次分配,分配後,統計那些有max-width[height]
的items, 分配後是否有超出的剩餘空間,然後對這些剩餘空間再分配給那些沒有設定max-width[height]
的item
再分配流程
min-width[height] 情況下 flex-shrink 的計算流程
由於可能存在某一個或多個item 設定了有min-width[height]
。所以,CSS引擎會先進行一次 shrink, shrink後,統計那些有min-width[height]
的items, shrink後是否有的剩餘的未 shrink空間,然後對這些剩餘空間再分配給那些沒有設定min-width[height]
的item。
注意:第一次 shrink的演算法和第二次分配未 shrink剩餘空間的演算法不同!
總結
Flexbox 佈局很棒。免去了我們大量關於適配方面的工作,但是深入理解,並用好它還是需要一點門檻的。 再次感謝 @大漠老師 的鼎力幫助,謝謝。
相關文章
- 簡單理解flex佈局Flex
- 你真的理解 flex 佈局嗎?Flex
- flex 佈局Flex
- Flex佈局Flex
- Flex 佈局:個人的學習與理解Flex
- CSS > Flex 佈局中的放大和收縮計算CSSFlex
- CSS佈局–聖盃佈局和雙飛翼佈局以及使用Flex實現聖盃佈局CSSFlex
- flex佈局(彈性佈局)Flex
- css flex佈局 精確計算成員寬度值CSSFlex
- 浮動佈局 和 flex佈局Flex
- css flex佈局CSSFlex
- flex佈局原理Flex
- 深入理解聖盃佈局和雙飛翼佈局
- 重溫 Flex 佈局Flex
- Flex佈局應用Flex
- flex佈局屬性Flex
- flex 佈局:語法Flex
- 淺談Flex佈局Flex
- flex佈局實戰Flex
- html的flex佈局?HTMLFlex
- flex佈局筆記Flex筆記
- flex佈局學習Flex
- flex佈局——轉載Flex
- ReactNative flex 佈局ReactFlex
- CSS display: flex佈局CSSFlex
- Flex佈局-子項Flex
- flex彈性佈局 響應式佈局Flex
- dispaly的Grid佈局與Flex佈局Flex
- 【譯】Flutter | 深入理解佈局約束Flutter
- 深入理解行內元素的佈局
- Flex-彈性佈局Flex
- display:flex 彈性佈局Flex
- 玩遊戲 學Flex佈局遊戲Flex
- 移動端flex佈局Flex
- css3 flex 佈局CSSS3Flex
- CSS關於flex佈局CSSFlex
- [面試專題]Flex 佈局面試Flex
- Flex 佈局語法教程Flex