Flexbox 佈局中不爲人知的細節

Flexbox 佈局 已是目前最爲流行的 Web 佈局方式之一,它給 Web 開發者在完成頁面或組件的 UI 佈局帶來了極大的靈活性和便利性。但也是因爲它有極大的靈活性,裏面隱藏了一些不爲人知的細節,如果不是對 Flexbox 極爲熟悉或者對其規範極爲了解的話,其中有很多細節將會被遺漏,而這些細節又會讓你在使用的感到困惑,甚至是帶來一定的麻煩。

這次在優化 imgcook 的 Flexbox 佈局時,重新閱讀了一次 Flexbox 的規範,發現自己曾忽略了部分重要信息。爲此在這篇文章中,將 Flexbox 佈局,CSS 的書寫模式,邏輯屬性,對齊方式結合在一起整理了一篇筆記,希望對於想了解或使用 Flexbox 碰到痛楚的同學有所幫助。

一些術語和概念

Flexbox 術語

術語的統一有助於我們後面更好的討論和解決問題。用下圖來描述 Flexbox 中的術語:

主軸和側軸只有在 Flexbox 佈局體系中才有這樣的概念,並不是水平方向永遠都是主軸(Main Axis),垂直方向永遠是側軸(Cross Axis)。主軸和側軸除了會受 Flexbox 中的 flex-direction 取值的影響之外,還會受 CSS 的書寫模式 writing-modedirection 以及 HTML 的 dir 屬性的影響!

塊軸和內聯軸

CSS Box Alignment Module Level 3 引入了兩個新的概念,即 塊軸(Block Axis) 內聯軸(Inline Axis) :

同時 塊軸(Block Axis)又常稱爲列(Column), 內聯軸(Inline Axis)又常稱爲行(Row):

雖然目前爲止在 Flexbox 規範和 Grid 規範中都有自身關於對齊方式的描述,但 CSS 中有關於對齊方式都將收口到 Box Alignment 模塊中;爲此後面對說軸的說法更多的是 “ 塊軸” 和 “ 行內軸”,結合到 Flexbox 佈局中,軸的對應關係是:

塊軸和行內軸同樣受 CSS 的書寫模式 writing-modedirection 以及 HTML 的dir 屬性影響。只不過,在 Flexbox 佈局中,還受 flex-direction 屬性的影響

書寫模式

CSS Writing Modes Level 3 規範中的 writing-modedirection 以及 HTML 中的 dir 屬性對於 Flexbox 中的主軸和側軸都會有影響,將會改變主軸和側軸的方向。

邏輯屬性

塊軸內聯軸書寫模式 的出現之後,就有了 塊起點(Block Start)、塊終點(Block End)、內聯起點(Inline Start)和 內聯終點(Inline End):

如果放到 Flexbox 佈局中:

同時 CSS Logical Properties and Values Level 1 規範中引入了 block-startblock-endinline-startinline-end , 但它們和 Flexbox 中,Grid 中以入 Box Alignment 中的 flex-startstartflex-endend 是不等同的,也不是同一領域中的概念。這幾個屬性對應的是物理屬性中的 toprightbottomleft

也就是說,引入 CSS 邏輯屬性之後,CSS 盒模型將分 物理盒模型邏輯盒模型 :

CSS 屬性也從此之後有 邏輯屬性物理屬性 之分:

注意,CSS 邏輯屬性也受 CSS 書寫模式 writing-modedirectioin 屬性和 HTML 的 dir 屬性影響,而且不同組合之下也不同:

剩餘空間(可用空間)和 不足空間

在 Flexbox 佈局模塊中,Flex 容器中可能會包含一個或多個 Flex 項目。而 Flex 容器和 Flex 項目都有其自身的尺寸大小,那麼就會有 Flex 項目尺寸大小之和大於或小於 Flex 容器的情景:

Flex 容器 和 Flex 項目

在元素上使用 display 設置值爲 flexinline-flex ,該容器會成爲 Flex 容器,該容器下的子元素,包括 文本節點,僞元素。

使用 flexinline-flex 的具體場景:

如果元素顯式設置了 display 的值爲 flexinline-flex ,Flex 項目在未顯式設置與尺寸大小有關的屬性時,Flex 項目都將會按其內容大小來計算自身大小。

當 Flex 容器中所有 Flex 項目所有寬和大於 Flex 容器時:

使用 display: inline-flex 時最好結合 min-widthmin-height 一起使用。不建議顯式設置 widthheight

display 設置爲 flex 時,Flex 容器從表現形式上類似於塊容器,事實它是一個 Flex 容器,上下文格式是 FFC(Flexbox Formatting Content),因此運用於塊容器(Block Formatting Content)上的一些佈局屬性就不再適用,比如:

有一點需要注意, 如果元素的 display的值爲 inline-flex ,並且該元素顯式的設置了 floatposition 的值爲 relativeabsolutefixed,那麼 display 的計算值是 flex, 即 Flex 容器表現行爲和 display: flex 等同

運用於 Flex 容器上的屬性

指定主軸方向

在 Flex 容器中顯式使用 flex-direction  可以指定主軸方向,如果未顯式設置 flex-direction 屬性,Flex 容器則會採用其默認值 row

上圖展示的僅是閱讀方式是 LTR(Left-to-Right),如無特殊聲明,接下來的文檔不會因閱讀方式(即 CSS 的 writing-modedirection 和 HTML 的 dir 屬性)列出不同的示意圖。

除非需要顯式的修改主軸方向,才需要在 Flex 容器上顯式設置 flex-directioin , 比如像下圖這種排版本方式:

_flex-direction: column _

row-reverse 和默認值 row 表現恰恰相反,適用於下面這樣佈局場景:

在 Flexbox 佈局中, flex-direction 在指定 Flex 容器主軸方向時,也會對 Flex 項目的排列順序有影響(在不改變 DOM 結構,要實現反方向排版時,非常適合)。除了 flex-direction 可以影響 Flex 項目排列順序之外,在 Flex 項目中顯式使用 order 屬性也可以,並且可以在不影響 DOM 結構,按照你自己任意想要的意圖進行排序。

目前在 imgcook 中使用 Flexbox 佈局時,在 Flex 容器上都會顯式的設置 flex-direction 的值,即使是默認值: row 。在佈局算法優化中,可以做相應的處理,只有在非 row 時纔在 Flex 容器上顯式設置 flex-direction

控制 Flex 項目是否換行(Flex 行)

使用 flex-wrap 可以控制 Flex 項目在 Flex 容器換行的方式:

只有所有 Flex 項目寬度總和大於 Flex 容器主軸尺寸時,設置 flex-wrap 屬性才能生效。

flex-wrap 取值爲非 nowrap (即 wrap  和 wrap-reverse )都可以讓 Flex 項目換行(列)顯式,其中 wrap-reverse 表現行爲和 wrap 剛好相反。

flex-directionflex-wrap 可以簡寫成 **flex-flow **。 flex-flow 使用時可以只顯式設置一個值,也可以顯式設置兩個值:

主軸方向對齊方式

在 Flex 容器中使用 justify-content 來控制 Flex 項目在 Flex 容器主軸方向的對齊方式,也可以用來分配 Flex 容器中主軸方向的剩餘空間。使用 justify-content 分配 Flex 容器剩餘空間,主要是將剩餘空間按不同的對齊方式,將剩餘空間分配給 Flex 項目的兩側,即控制 Flex 項目與 Flex 項目之間的間距。

justify-content 存在兩個規範中:

在 Flexbox 佈局模塊中, justify-content 取值只要有以下六種:

需要注意 space-betweenspace-aroundspace-evenly 三者的差異:

如果 Flex 容器沒有額外的剩餘空間,或者說剩餘空間爲負值時, justify-content 的值表現形式:

在 Flexbox 佈局中,可以使用這些屬性很好控制 Flex 容器的剩餘空間,比如:

側軸方向對齊方式

在 Flexbox 容器中使用 align-items 來控制 Flex 項目在側軸方向的對齊方式。

align-items 的默認值是 stretch ,但只有 Flex 項目示顯式設置 height (或 width ) 值,Flex 項目纔會被拉伸填滿整個 Flex 容器。

如果 Flex 容器沒有剩餘空間或剩餘空間爲負值是:

多行(列)對齊方式

align-content 只適用於 Flex 容器在沒有足夠空間(所有 Flex 項目寬度之和大於 Flex 容器主軸尺寸),並且顯式設置 flex-wrap 的值爲非 wrap 時。

align-content 表現行爲有點類似於 justify-cotent 控制 Flex 項目在主軸方向的對齊方式(分配 Flex 容器主軸剩餘空間),而 align-content 可以用來控制多行狀態下,行在 Flex 容器側軸的對齊方式(分配 Flex 容器側軸剩餘空間)。可以把 align-content 狀態下側軸中的整行當作是 justify-content 狀態下單個 Flex 項目。

align-content 還有一點不同之處,多了一個 stretch 值。當 Flex 容器中所有行的尺寸之和大於 Flex 容器側軸尺寸(Flex 容器側軸沒有可用空間或可用空間爲負值)時,各值表現行爲:

間距(行與行,列與列)

gap 用來控制 Flex 項目之間的間距,但會忽略 Flex 項目與 Flex 容器邊緣的間距:

運用於 Flex 項目的屬性

Flex 項目自身對齊方式

在 Flex 容器上可以使用 justify-contentalign-content 以及 align-items 分配 Flex 容器主軸和側軸的空間(控制 Flex 容器中所有 Flex 項目對齊方式)。如果你需要對 Flex 項目個體對齊方式做處理,可以使用 align-self

align-self 取不同值的效果:

Flex 項目的 align-self 顯式設置值爲 auto 時不會覆蓋 Flex 容器的 align-items ; 另外如果在 Flex 項目上顯式設置 margin 的值爲 auto 時,Flex 項目的 align-self 值將會失效

類似上圖這樣的場景, align-self 就非常實用。

Flex 項目排序

在 Flex 容器中使用 flex-direction 可以對 Flex 容器中的所有 Flex 項目按 “ LTR”、“ RTL”、“ TTB” 或 “ BTT” 方向排列。

在 Flex 項目上,還可以使用 order 指定具體的數值,在不改變 DOM 結構之下對 Flex 項目進行排序,其中數值越大,越在往後排:

在一些左右,上下互換順序的時候,除了 flex-direction 之外,還可以在 Flex 項目設置 order

Flex 項目伸縮計算

Flex 項目中使用 flex 屬性可以根據 Flex 容器的可用空間對自身做伸縮計算,其包含三個子屬性: flex-basisflex-shrinkflex-grow 。這幾個屬性都有其初始值:

flex 的三個子屬性: flex-grow (擴展比率)、 flex-shrink (收縮比率)和 flex-basis (伸縮基準)。這三個屬性可以控制 Flex 項目,具體的表現如下:

flex 屬性可以指定 1 個值(單值語法)2 個值(雙值語法)  或 3 個值(三值語法)

單值語法:值必須爲以下其中之一:

雙值語法:第一個值必須爲一個無單位數值,並且它會被當作 <flex-grow> 的值;第二個值必須爲以下之一:

三值語法:

flex 屬性的取值可以是:

flex-grow 計算

flex-grow 計算公式:

示例:

假設 Flex 容器中有四個 Flex 項目,具體參數:

計算過程:

計算出來的結果:

flex-grow 的取值還可以是 小數值 。如果將上面示例中的 flex-grow 的值分別換成 00.10.20.3 ,這個時候 flex-grow 的總和(所有 Flex 項目的 flex-grow 和)就是 0.6  ,該值小於 1 。這個時候,Flex 項目同樣會根據 flex-grow 增長因子來瓜分 Flex 容器的剩餘空間,Flex 自身寬度也會變大,但 Flex 容器的剩餘空間不會被全部瓜分完,因爲所有 flex-grow 和小於 1 。就該示例下,只瓜分了 Flex 容器剩餘空間寬度的 60%

如果 flex-grow 和小於 1 , 其計算公式如下:

即使 Flex 容器中所有 Flex 項目的 flex-grow 和大於 1 ,但也不可以絕對地說,Flex 項目可以根據自身的 flex-grow 所佔比率來瓜分 Flex 容器的剩餘空間。因爲元素的尺寸會受 max-width 的影響。當 Flex 項目顯式設置了 max-width的值時,當 Flex 項目根據flex-grow計算出來的寬度大於 max-width時,Flex 項目會按 max-width的值爲準。比如我們在前面的示例上,給所有 Flex 項目設置一個 max-width 的值爲 18vw ,此時計算過程和結果如下:

這個時候 Flex 容器剩餘空間並沒有全部用完, 40vw - 0vw - 6.667vw - 8vw - 8vw = 17.333vw ,即 Flex 容器還有 17.333vw 的剩餘空間。

如果 Flex 項目沒有顯式設置與寬度有關的屬性(包括 flex-basis ),那麼 flex-grow 在計算時,Flex 項目會按其內容的寬度來計算。

從上圖可以得到:

將相應的值套用到 flex-grow 的公式中,可以得到:

注意,不同的瀏覽器對小數處理有差異。

flex-shrink 計算

flex-shrink 計算公式:

示例:

假設 Flex 容器有四個 Flex 項目,具體參數如下:

計算過程:

計算出來的結果:

flex-shrink 的計算還可以甚至另一個公式來計算:

flex-shrinkflex-grow 類似,也可以取小數值。如果 Flex 容器中所有 Flex 項目的 flex-shrink 總和小於 1 ,那麼 Flex 容器的不足空間就不會被 Flex 項目按收縮因子瓜分完,Flex 項目會依舊會溢出 Flex 容器。

flex-shrink 總和小於 1 時,其計算公式如下:

基於上面的示例,把 Flex 項目的 flex-shrink 分別設置爲 00.10.20.3 ,計算過程如下:

即使 Flex 容器中所有 Flex 項目的 flex-shrink 和大於 1 ,但也不可以絕對地說,Flex 項目可以根據自身的 flex-shrink 所佔比率來瓜分 Flex 容器的不足空間。因爲元素的尺寸會受 min-width 的影響。當 Flex 項目顯式設置了 min-width的值時,當 Flex 項目根據 flex-shrink計算出來的寬度小於 min-width時,Flex 項目會按 min-width的值爲準。比如我們在前面的示例上,給所有 Flex 項目設置一個 min-width 的值爲 10vw ,此時計算過程和結果如下:

在這個情況之下,Flex 項目的最終寬度總和還是會大於 Flex 容器寬度,Flex 項目同樣會溢出 Flex 容器。

flex-shrinkflex-grow 還有一點相似,那就是未顯式給 Flex 容器的 Flex 項目顯式設置與寬度有關的屬性時,那麼 Flex 項目的初始寬度會以其內容的寬度作爲基準計算值。

flex-shrink 有一點和 flex-grow 完全不同,如果某個 Flex 項目按照 flex-shrink 計算出來的新寬度趨向於 0 時,Flex 項目將會按照該元素的 min-content 的大小來設置寬度,同時這個寬度將會轉嫁到其他 Flex 項目,再按相應的收縮因子進行收縮。

比如我們將第四個 Flex 項目的 flex-shrink 的值從 3 改爲 9 。根據上面提供的公式,可以獲知,Flex 項目 4 的新寬度等於 15vw - (20vw ÷ 12) × 9 = 0

計算出來的寬度爲 0 ,但實際上這個時候渲染出來的寬度是該項目的 min-content (該示例就是 “shrink” 單詞的寬度,如下圖所示),大約 47.95px (約 3.66vw )。那麼這個值將會分成 3 份(因爲該例另外三個 Flex 項目的 flex-shrink012 ),並且對應的 Flex 項目會繼續分配本應 Flex 項目 4 要收縮的寬度。即:

瀏覽器視窗寬度在 1310px 狀態下渲染出來的結果如下:

在 Flexbox 佈局模塊中,基於前面提到的 Flex 容器的對齊屬性、Flex 項目中的 flex-shrinkflex-grow 我們就可以很好的處理 Flex 容器的剩餘空間和不足空間:

具體的我們可以繪製一張這方面的流程圖:

flex-basis 計算

flex-basis 的計算相對於 flex-growflex-shrink 更略爲複雜,因爲它和 Flex 項目的 內容(Content) 、** width **、 **min-width **和 **max-width** 都有關係。這裏的關係指的就是**它們之間的權重關係**,簡單地說,**在 Flex 項目中同時出現這幾個屬性時,最終由誰來決定 Flex 項目的寬度**。

在 Flexbox 佈局中,可以使用 flex-basis 來實始化 Flex 項目尺寸,即 在任何 Flex 容器空間(剩餘空間或不足空間)分配發生之前初始化 Flex 項目尺寸

事實上,在 Flexbox 佈局模塊中 設置 Flex 項目的尺寸大小存在一個隱式的公式:

content  ➜ width  ➜ flex-basis

簡單地說,如果 Flex 項目未顯式指定 flex-basis 的值,那麼 flex-basis 將回退到 width (或 inline-size )屬性;如果未顯式指定 width (或 inline-size )屬性的值,那麼 flex-basis 將回退到基於 Flex 項目內容計算寬度。不過,決定 Flex 項目尺寸大小,還受 flex-growflex-shrink 以及 Flex 容器大小的影響。而且 Flex 項目 最終尺寸 會受 min-widthmax-width(或 min-inline-sizemax-inline-size ) 屬性限制。這一點必須得注意。

來看一個示例:

1<div>
2    <div></div>
3    <div></div>
4    <div></div>
5    <div></div>
6</div>
7 
8
1.flex__container {
2    width: 600px;
3    display: flex;
4
5    border: 1px dashed #f36;
6    align-items: stretch;
7}
8 
9

Flex 項目不顯式的設置任何與尺寸大小有關係屬性,即用 content來撐開 Flex 項目

1<div>
2    <div>Lorem ipsum dolor sit amet</div>
3    <div>Lorem ipsum dolor sit amet consectetur adipisicing elit</div>
4    <div>Fugiat dolor nihil saepe. Nobis nihil minus similique hic quas mollitia.</div>
5    <div>Lorem ipsum dolor sit amet consectetur adipisicing elit. Molestias consequuntur sequi suscipit iure fuga ea!</div>
6</div>
7 
8

在這個示例中,並沒有顯式給 Flex 項目設置 flex-basis 屬性,此時 flex-basis 會取默認值 auto

顯式給 Flex 項目設置 width 值。

1:root { 
2  --width: 120px; 
3} 
4
5.flex__item { 
6  width: var(--width); 
7}
8 
9

這個時候所有 Flex 項目寬度都是相等的:

瀏覽器計算出來的 flex-basis 值依舊爲 auto ,但顯式的設置了 width: 120px ,最終 width 屬性的值決定了 Flex 項目的尺寸大小。

顯式給 Flex 項目設置 flex-basis 值,即 Flex 項目同時有 widthflex-basis

 1:root {
 2    --width: 120px;
 3    --flexBasis: 150px;
 4}
 5
 6.flex__container {
 7    width: 800px;
 8}
 9
10.flex__item {
11    width: var(--width);
12    flex-basis: var(--flexBasis);
13}
14 
15

雖然在 Flex 項目同時顯式設置了 widthflex-basis ,但 Flex 項目最終的尺寸大小採用了 flex-basis 的值:

在 Flexbox 佈局模塊中影響 Flex 項目尺寸大小應該根據其隱式公式 (即 content  ➜ width  ➜ flex-basis  )來進行判斷。如果要顯式給 Flex 項目設置尺寸大小,其最佳方式是 使用 flex-basis ,而不是 width (或 inline-size )。

最後還有一點千萬別忘記:

使用 flex-basis 時會受min-widthmax-width(或邏輯屬性中min-inline-sizemax-inline-size )的限制

在 CSS 中,如果元素同時出現 widthmin-widthmax-width 屬性時,其權重計算遵循以下規則:

如果 Flex 項目同時出現 widthflex-basismin-width 時,具體的運算過程如下:

這樣一來,如果 flex-basis 小於 min-width 時,Flex 項目的寬度會取值 min-width ,即 min-width 覆蓋 flex-basismin-width勝出)

如果 Flex 項目同時出現 widthflex-basismax-width 時,具體的運算過程如下:

這樣一來,如果 flex-basis 大於 max-width 時,Flex 項目的寬度會取值 max-width ,即 max-width 覆蓋 flex-basismax-width 勝出)

如果 Flex 項目同時出現 widthflex-basismin-widthmax-width 時,會在上面的規則上增加新的一條規則來進行判斷:

那麼套用到 Flex 項目中:

由於 min-width 大於 max-width 時會取 min-width ,有了這個先取條件我們就可以將 flex-basismin-width 做權重比較,即:** flex-basis 會取 min-width 。反過來,如果 min-width 小於 max-width 時則依舊會取 max-width ,同時要是 flex-basis 大於 max-width 就會取 max-width **。

如果你理解了的話,可以使用更簡單的規則來決定用於 Flex 項目的尺寸。

首先根據 content  ➜ width  ➜ flex-basis 來決定用哪個來決定用於 Flex 項目。如果 Flex 項目顯式設置了 flex-basis 屬性,則會忽略 contentwidth 。而且 min-width 是用來設置 Flex 項目的下限值; max-width 是用來設置 Flex 項目的上限值。

用一個簡單的流程圖來描述:

注,Flex 項目上的 flex-shrinkflex-grow 也會影響 Flex 項目尺寸大小

Flex 項目上的 margin

在 Flex 項目顯式設置 margin 的值爲 auto 可以靈活的控制單個 Flex 項目在 Flex 容器中的位置:

比如像下圖這樣的效果,使用 margin-left: auto 就非常的實用:

案例整理

padding 與自動寬問題

針對這個案例, 較好的方案對於內部元素不顯式設置任何關於 paddingmargin 的屬性。人工實現可能會像下面這樣:

1<div>
2  <span>卷</span>
3  <span></span>
4  <span>¥1000</span>
5</div>
6 
7
 1.flex__container {
 2  display: inline-flex;
 3  min-width: 200px;
 4  height: 60px;
 5  
 6  border: 1px solid rgba(255, 0, 54, 1);
 7  background-color: rgba(255, 0, 54, 0.1);
 8  border-radius: 4px;
 9  color: #ff0036;
10  font-size: 24px;
11  font-weight: 400;
12}
13
14.flex__container > span {
15  display: inline-flex;
16  justify-content: center;
17  align-items: center;
18}
19
20.divider {
21  border-right: 1px dashed currentColor;
22}
23
24.coupon {
25  min-width: 50px;
26}
27
28.price {
29  flex: 1;
30  min-width: 0;
31  padding: 0 10px;
32}
33 
34

小結

這篇筆記涉及到了 Flexbox 規範中的大部分內容以及一些臨界點,在使用 Flexbox 來完成 UI 上的佈局除了文章中提到的一些基礎內容和細節之外,還有一些其他的東西。比如 Flex 容器中的定位,層級計算等,Flex 容器和 Flex 項目碰到overflow以及 Flex 容器中的滾動計算等。這些對於場景具有較強的指定性,對於邊界的處理也過於複雜。在我們平常使用 Flexbox 很少甚至不怎麼會碰到。因此沒有在文章中羅列。

如果你在使用 Flexbox,特別是在使用 imgcook 自動還原 UI,效果和你預期不一樣,或者有不合理的地方,都可以隨時來撩偶。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://juejin.cn/post/6940498706143641613?utm_source=gold_browser_extension