本篇會有不少冷門範例。
其實我覺得很奇妙,就是我老是踩到一些超冷門連 Google 都找不太到的雷。
Flexbox 能與不能
首先還是得叮嚀一下,如果使用靜態定位樣式,會打破 Flex 容器軸流向,所以請不要覺得用了 position: absolute
, position: static
, position: fixed
或 position: sticky
的時候來問為何 Flexbox 沒有效果。
尺寸
無論是 Flex 容器或是 Flex 元件,尺寸這件事情在設計或是設定上都會有一定的 毛病 限制,首先我們先來看看 Flex 容器的尺寸。
在各種 CSS 框架上,預設的容器軸都以 row
為主,而這樣的容器可能會遇到的狀況會有哪些呢?
- 容器的剩餘空間不如預期。
- 對齊點的狀況不如預期。
- Flex 元件超出容器。
- Flex 元件壓縮不如預期。
- Flex 元件強迫換行的狀況。
writing-mode
的影響。
當然,如果換為 column
為主軸,大概會追加以下狀況,
- Flex 元件無法換行。
flex-basis
設定不如預期。stretch
設定不如預期。
首先,關於容器尺寸這件事情,我們在 row
的情況下,由於我們的內容邊界都是以顯示器邊界為主,所以,你在以 row
的設定狀況下,並不用特別擔心 width
的狀況。但是,由於 nowrap
是一個預設的行為,所以通常會遇到 Flex 元件超出去的情形。
此時,如果你的容器搭配了 overflow: hidden
的話,那麼元件本身就會被容器切斷而無法完整顯示。這個情況跟一般使用區塊元件的情形是相同的。
在這個時候,倘若你的 Flex 元件有設定 flex-shrink
的話,那麼在可允許的範圍內,Flex 元件就會被壓縮到主軸尺寸內。
請注意,是可允許的範圍內。
為什麼要這樣說?因為 flex-shrink
在壓縮元件的情況下,無法壓縮到比 content
這個尺寸還要小的尺寸,舉個例子來說,
.flex-item {
flex: 1 1 10rem;
}
在這種情況下,你可以搭配 max-width
來限制 Flex 元件的大小,不過這有個但書,我們容後再述,
.flex-item {
flex: 1 1 20rem;
max-width: 20rem;
}
總結來說,當你的內容會超出元件寬度時,你的 flex-shrink
會將內容尺寸當作是最小尺寸來使用。當然,我們可以將內容強迫斷行來達成顯示正常的目的。
.flex-item {
flex: 1 1 20rem;
word-break: break-all;
}
接著來說一下剛剛的但書。我們在大多數的 CSS 框架中(以 Bootstrap 5.0.x 為例),其實比較常見的設定會類似這樣,
.col-sm-3 {
flex: 0 0 auto;
width: 25%;
}
通常是不使用 flex-shrink
, flex-grow
這個設定的,也就是說不要壓縮、不擴充元件。所以,當你想要壓縮元件,又想搭配 flex-grow
的話,那麼這個 max-width
會是一個很難處理的事情。簡單來說,你會遇到這樣的狀況,
- 容器放大時,
flex-grow
會踩到max-width
而停止填滿容器。 - 容器縮小時,
flex-shrink
因為內容寬度,也會踩到max-width
而產生非預期尺寸。 - 以上的問題換成
min-width
也會有類似的狀況。
所以,總括來說,Flex 元件請單純拿來排版就好,不然各大 CSS 框架也不會將 Flex 元件的樣式寫的那麼簡單,以 Bootstrap 5.0.x 來說,就只做一件事情,
用
width
硬幹flex-basis
,其他都停用。
也由於預設不要壓縮、不擴充元件的關係,所以大多數框架都會設定 flex-wrap: wrap
來讓 Flex 元件能夠斷行。當然,斷行也是會有斷行的問題(笑)。
對了,我好心提醒一下,有在用 Bulma.io 的人,他預設是 fex: 1 1 0;
,另外 Windi CSS 的 flex-1
預設是 flex: 1 1 0%
,所以上面講的狀況他都會遇到喔(啾咪)。
我沒有要酸 Bootstrap 的意思,後面講到 Grid 我也會拿 tailwindcss 來嘴一下。
ps. Bootstrap 5.1.0 之後也開始使用 Grid Layout Module 了(啾咪)。
留白與斷行
首先我們來說說斷行這件事情,在 flex-wrap: wrap
的設定下,任何超過容器寬度的元件都會被換行。然而,以 flex-direction: row
的設定來說,如果沒有特別指定容器寬度的話,基本上就是以裝置的寬度來看。
好的,那麼斷行會受到哪些行為的影響呢?
flex-basis
width
,min-width
,max-width
margin
,padding
- 容器擬似元件
::before
,::after
box-sizing
非border-box
下,border
會產生影響
在一般的情況下,我們容器基本上是沒有間隔(gap)的。我在本系列文章的圖片僅是為了識別方便而將每個 Flex 元件與容器之間加上間隔。正常來說,這是我們的 Flex 真實樣貌,
由於我們使用的是 box-sizing: border-box
,所以 Flex 元件的尺寸包含了框線。如果你使用的是 content-box
的話,那麼 border
就必須納入考慮。在此就不多說這個老掉牙的設定了。
回到 Flex 容器上,我們先來看看留白(padding
)這件事情。倘若你在 Flex 容器中使用了 padding
樣式,那麼你的軸尺寸就必須要扣掉樣式值,這個理論上來說 應該是常識。基於這個常識,如果你的 Flex 元件使用了絕對尺寸,那麼請確保呈現結果是符合你的需求的。
同樣是留白,我們再來看 Flex 元件上面的留白(margin
)。通常我們在區塊元件(Box Model)或同等於區塊格式的內容上,所謂的留白(margin
)會有一個特性,
垂直方向相鄰元件的
margin
會收合(Collapse)。
關於 margin
各種收合的事情,可以參考這一篇文章,寫得非常詳盡。
回到 Flex 元件上,在 Flex 元件上面使用 margin
就沒有這種 效果 了,原因只有一個,
Flex 元件並不是區塊格式。
Flexible is flex formatting context, not block formatting context.
所以,即便你在 Flex 元件上宣告 display: block
也是沒有什麼效果的,他不會因此而轉變為 Block formatting context。
也就因為如此,所以當你在設定 Flex 元件尺寸時,就必須把 margin
納入考慮,否則他會在一些很奇怪的地方出現斷行。
所以,如果你真的想要使用 margin
來推開 Flex 元件的話,在 flex-basis
或是 width
, max-width
當中,請使用計算寬度來達成你的目的。
.flex-item {
flex: 0 0 auto;
// 不要問我為什麼 - 40px
width: calc(50% - 40px);
// 使用 margin 來留白
margin: 0 20px;
}
但是,不太建議這麼做。
原因用圖片來解釋比較清楚,
由於 Flex 容器本身沒有間隔(gap)的概念,所以倘若想使用 margin
的方式來製作這個間隔,就會產生相鄰元件間隔較大的結果。所以你在各大框架當中,幾乎沒有人會在 Flex 元件中使用 margin
留白來產生間隔。另,關於 gap 這件事情我後面會提到。
當然,特殊位移需求除外,例如 offset-3
這種。
最後,回到 Flex 容器本身,倘若你使用擬似元件,請把他當成 Flex 元件來處理。
不如預期的部分
其實上面已經講了不少,接下來這些可能比較冷門一點,大家可以加減聽聽看。
- 垂直方向(
column
)的換行 flex-basis: 100%
真的可以強迫換行嗎?stretch
怎麼了?- 關於
writing-mode
這件事
首先,打從一開始我們就只說明了 flex-direction: row
這件事情,現在來講一些關於 flex-direction: column
的事情。故名思義就是主軸、交叉軸對調。
那麼,我前面有提到,這兩個軸線對調的話,那麼有些屬性應用的方向也會跟著軸轉動。所以說,上面那些 水平 方向會發生影響的事情,在這個 垂直 方向上面,也一樣會受到影響。
垂直方向最常遇到的就是斷行問題。
斷行不是問題,問題是沒有固定高度。
然而,平常我們在設計頁面時,所謂的「高度」這件事情並不是一個固定的尺寸,換句話說,你的容器就沒有所謂的 主要軸尺寸 這件事情。換個角度來看,就是你擁有一個「無限寬」的螢幕,然後你的內容理所當然的就 無法斷行。
同樣的道理,雖然我們可以在 row
當中使用 flex-basis: 100%;
來斷行,當你將主要軸旋轉為 column
時,在沒有固定主要軸尺寸的狀況下,所謂的 flex-basis: 100%
基本上是無效的。這一點就如同,你在一個動態高度的內容當中,設定 height: 100%
是沒有意義的意思是相同的。
至於有沒有其他解法?有的。
我之前寫過相關的文章 [CSS] 瀑布流難題
老文章就不拉過來占版面了,後續還是會有機會提到相關的事情。
接下來我們來聊聊 stretch
這件事情。他主要會被應用的 方向 都是發生在交叉軸上面,所以無論你的方向是 row
或 column
,請注意這個關鍵字都是應用在交叉軸上。
所有應用在交叉軸上的東西都有一個必須存在的東西,
交叉軸尺寸
所以,一旦你在沒有定義交叉軸尺寸的 Flex 容器內,無論是 align-content: stretch;
或 Flex 元件的 align-self: stretch;
,都無法產生任何效果。即便你的容器符合所謂的多行(或多欄)的情況,只要交叉軸沒有固定尺寸,他就不會產生任何效果。
另外,若你使用了 align-self
的話,請與 align-items
搭配使用,效果會比較顯著一點(笑)。
最後,來稍微提一下 writing-mode
這個樣式。這個樣式其實相當冷門,多數是使用在雙位元字的情況下,詳細的說明可以參考官方的文件。
那麼到底跟 Flex 有什麼關係呢?具體來說,writing-mode
是決定文字流的樣式設定,但是,他也會干涉在 Flex 容器內的元件流向,舉例來說,
當你在容器內加上 writing-mode: vertical-lr
之後,文字流向就會改變,同時,你的 Flex 元件流向也會跟著改變。在這個情況下,你的 Flex 容器會有以下改變:
- 主要軸、交叉軸與 Flex 容器流向相同。
- 文字流向會跟著
writing-mode
樣式。 - Flex 元件流向也會跟著
writing-mode
。 - 承上,Flex 容器對於元件的軸流向應用會交換。
- 承上,Flex 元件流向對容器軸的應用樣式也會交換。
具體來說,加上 writing-mode: vertical-lr
跟你直接寫 flex-direction: column
,對於 Flex 容器(或元件)來說,沒有什麼太大的區別(畢竟該交換的都會交換)。聽起來很複雜,當然在沒有其必要性的狀況下,也不建議你這樣做。當然,如果要做一些奇技淫巧的時候是可以這樣寫啦(笑)。
小記
這邊有一個地方可以玩一下 Flexbox 的各種設定,
Flexbox Playground
其實不要把 Flex 想得太複雜或是很萬能,他就是一個讓你規劃跟結構有關的事情,回到 HTML5 的本意,你該是用 table
的地方還是請你用 table
就好,硬是要用 div
畫出一個表格來也不是不行,但明明就是用 table
很方便的事情,為何還要用 Flexbox 來畫呢?
目錄與小節:
[CSS] Flex/Grid Layout Modules, part 0