跳至主要内容

[CSS] FlexBox Layout

TL;DR

  • 使用 flexbox 的時候盡可能加上 flex-wrap: wrap;,讓 flex-container 如果容納不下 flex-items 的話,flex-items 不會穿出去,而是會換行
/* for flex container */
.flex-container {
justify-content〈流向軸內容對齊方式〉:
flex-start(default) |
flex-end |
center |
space-between |
space-around

align-items〈垂直軸對齊方式-單行〉:
flex-start |
flex-end |
center |
baseline |
stretch(default)

align-content〈垂直軸對齊方式-多行〉:
flex-start(default) |
flex-end |
center |
space-between |
space-around

flex-direction〈流向軸方向〉:
row(default) |
row-reverse |
column |
column-reverse

flex-wrap〈換行與否〉:
nowrap(default) |
wrap |
wrap-reverse

flex-flow: <flex-direction> <flex-wrap>
}

flex-item 的屬性觀察重點:

  1. 先看每個 flex-item 的 flex-basis 是多少,如果有多出來的則會透過 flex-grow 分配;如果空間不足,則會都過 flex-shrink 分配
  2. 預設 flex-basis 是 auto,也就是會隨內容寬成長。
/* for flex-item */
.flex-item {
align-self: auto | flex-start | flex-end | center | baseline | stretch;
order: 0; // order 的用法類似 z-index,預設是 0
flex-grow: 0; // (default:0)
flex-shrink: 1; // (default:1)
flex-basis: auto; // (default:auto)
flex: <flex-grow> <flex-shrink> <flex-basis>; // (default: 0 1 auto)
}

See the Pen Flexbox Playground by PJCHEN (@PJCHENder) on CodePen.

觀念

  • 當我們使用 display: flex 其實使用的是 display: box flex;,也就是這個元素會以 block 的方式排版,但內部的內容會用 flex 的方式排版。
  • 看到 align-item 是針對當行; 看到 align-content 則是針對多行的情況

Flex Container

Flex Wrap (default: no-wrap)

  • flex-container 沒有下 flex-wrap: wrap; 時,預設會是 no-wrap
  • 如果有足夠的空間時,則所有的 flex-item 會以它自身的寬度為最大寬度(因為預設是 flex-basis: auto;)但若沒有足夠的空間時,則 flex-item 會根據比例縮小(因為預設是 flex-shrink: 1;),不會維持所給定的寬度。

Flex Item

Flex 的縮寫方式

flex 這個 CSS 屬性可以用來定義 flex-container 中有多餘空間時要如何成長或縮小,這是flex-grow, flex-shrinkflex-basis 的縮寫。

重點:先看每個 flex-item 的 flex-basis 是多少,如果有多出來的則會透過 flex-grow 分配;如果空間不足,則會都過 flex-shrink 分配。

Flex Basis

注意:flex-basis 是該 flex-item 的基準寬度,最後實際的寬度會受到 flex-shrinkflex-grow 而影響。

flex-basis: 0 就是 basis 寬度為 0 的意思。

Flex Grow 和 Flex Shrink

當父層 flex-container 的寬度,在扣除掉每一個 flex-item 的 flex-basis 的寬度後,如果有多出來的空間,則會透過 flex-grow 分配剩下來的空間;相反地,如果空間不夠的話,則會透過 flex-shrink 縮小不足夠的空間。

flex-basis 即使設為 auto 仍然會有寬度,其寬度就是內容的寬度。

Flex Basis 和 Width 的關係

content –> width –> flex-basis (limited by max|min-width)

簡單來說,當沒有設定 flex-basis 的情況下(預設是 auto),則會以該容器的所設定的 width 為準,如果該容器沒設定 width,則會以內容的寬度為準。這也就是為什麼沒有設定 flex-basis 的時候,該 flex-item 會等同於內容寬。

但若同時有設定 flex-basiswidth,那麼 flex-basis 的權力較大,會以 flex-basis 的設定為準,忽略 width 的設定,更精確來說,width 的效果只能作用在當 flex-basisauto 的情況下才有效。

:thumbsup: The Difference Between Width and Flex Basis @ Gedd

Flex Attributes Shorthand

下面的說明是在 Chrome 測試下的結果。

當只給一個值時:

// 什麼都不給的時候的預設值
flex: 0 1 auto;

// auto 會以 item 內容的寬高比例為依據,但會成長到佔滿整個多餘的空間,縮小到能夠符合容器的最小尺寸
flex: auto; // flex-grow: 1; flex-shrink: 1; flex-basis: auto;
flex: 1; // flex-grow: 1; flex-shrink: 1; flex-basis: 0%;

// 如果只有一個數值,但是有單位,表示 flex-basis
flex: 10em;
flex: 30px; // flex-grow: 1; flex-shrink: 1; flex-basis: 30px;

如果有兩個值時:

// 如果有兩個值,其中一個有單位,表示 flex-grow | flex-basis
flex: 1 30px; // flex-grow: 1; flex-shrink: 1; flex-basis: 30px;
flex: 0 30px; // flex-grow: 0; flex-shrink: 1; flex-basis: 30px;

// 如果有兩個值,都沒有單位,表示 flex-grow | flex-shrink
flex: 2 2; // flex-grow: 0; flex-shrink: 1; flex-basis: 0%;

如果給了三個值時:

// 表式: flex-grow | flex-shrink | flex-basis
flex: 2 2 10%;

Flex Item 寬度常見的問題

https://codepen.io/PJCHENder/pen/RNRewEL

[!tip] 快速檢查口訣

  • 縮得太小? 檢查有沒有 flex-shrink-0
  • 撐得太大? 檢查有沒有 min-w-0
  • 置中就不滿? 檢查有沒有補 w-full

1. 「對齊」會殺掉「拉伸」 (Alignment kills Stretch)

  • 原則: Flex 子元素預設是 stretch(撐滿)。但如果你設定了任何關於「對齊」的屬性(例如 mx-autoitems-centerjustify-center),子元素就會立刻收縮成內容寬度
  • 口訣: 想要置中又想要撐滿,請手動補上 w-full

2. 「內容」是頑固的 (Content is Stubborn)

  • 原則: Flex item 的預設 min-widthauto(表現得像 min-content)。這意味著「它絕對不會縮小到比內容(如一串文字或圖片)更窄」。這就是為什麼文字太長會撐破容器。
  • 口訣: 看到文字省略 (truncate) 失靈,直接給父層或該元素加 min-w-0

3. 「空間分配」是動態的,不是固定的

  • 原則: width 在 Flex 裡只是個「建議值」。當空間不足,Flex 會根據 flex-shrink 去擠壓你的寬度。
  • 口訣: 不想被壓扁?請下 flex-shrink-0(防禦式寫法)。
<div class="space-y-8 p-5">
<section>
<h3 class="font-bold mb-2">1. Flex-col and Margin Auto</h3>
<!-- 在 flex-col 下,父層預設有 align-items: stretch。這會強制子元素填滿寬度。 -->
<!-- 但當你設定 mx-auto 時,瀏覽器會優先處理邊距分配,導致元素收縮到其內容寬度 (Fit-content)。 -->
<!-- 這時解法是補上 w-full -->
<div class="flex flex-col border border-gray-300">
<div class="mx-auto w-full border border-blue-500 bg-blue-50">
<div class="bg-blue-300 mb-1">I am stretched because of w-full</div>
</div>
</div>
</section>

<section>
<h3 class="font-bold mb-2">2. Flex-item Shrink and Truncate</h3>
<div class="flex w-[300px] border border-indigo-500 bg-white">
<!-- 預設最大寬度只會是內容寬度,將沒辦法長滿 50px -->
<!-- 解法是加上 flex-shrink-0 -->
<div class="w-[50px] flex-shrink-0 bg-indigo-200 flex items-center justify-center">Fixed</div>

<!-- 由於要讓 truncate 生效,需要使用 w-full -->
<!-- 但這會使得 div 的寬度超過 250px,導致超出父層 container -->

<!-- 在 Flexbox 規範中,Flex item 的 min-width 預設值是 auto。 -->
<!-- 但在 Flex 環境下,這個 auto 的計算結果等同於 min-content(內容的最小寬度)。 -->
<!-- 這就是為什麼長文字會撐破容器,因為 Flex item 拒絕縮小到比內部文字還小的尺寸 -->
<!-- 解法是使用 min-w-0 -->
<div class="w-full min-w-0 border border-indigo-300 p-2">
<p class="truncate text-red-600">
這是一串非常長的文字,如果沒有 min-w-0,我就會把父容器撐開,導致右邊溢出。
</p>
</div>
</div>
</section>
</div>

重要:當 flex item 的寬度(大小)預設值

flexbox 的 flex item 有一個非常重要的假設,flex-item 的大小預設不能比裡面的內容寬度來的窄,因為 flex-item 的預設值是:

  • min-width: auto;(也就是說,最小寬度會是內容寬)
  • max-width: none;

以下面這個例子來說:

<div class="flex-container">
<div class="flex-item-sm">X</div>
<div class="flex-item-lg">
<span class="text-ellipsis">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis at voluptatibus
</span>
</div>
</div>

.flex-container 的寬是 300px,而 .flex-item-sm 的寬是 50px 時,我們會預期 .flex-item-lg 的寬最多只會是 250px,然而,這樣的假設並不一定正確。

在下面這個例子中,.flex-item-lg 的寬度變成了和父層的寬度一樣都是 300px,使得 flex-item-lg + flex-item-sm 的寬度超過了 .flex-container

https://codepen.io/PJCHENder/pen/LYLVdLj

Screen Shot 2022-04-05 at 8.53.26 PM

要解決這個問題嗎,可以在 .flex-item-lg 加上 min-width: 0;

同樣類似的問題還可以參考這個範例

參考