Learning Objectives
- 說明 flex-basis 是項目的基準尺寸,並用 flex-grow 的比例算出剩餘空間怎麼分
- 說明空間不夠時 flex-shrink 依「flex-shrink × 基準尺寸」的比例縮小,以及 flex-shrink: 0 的用途
- 寫出簡寫 flex: 1、flex: auto、flex: none 分別代表的三個值
- 用 margin-left: auto 與 align-self 調整單一項目,並說明 order 只改變視覺順序、不改變閱讀與鍵盤順序
- 用 Flexbox 做出導覽列、等高卡片列與頁尾貼底三種常見版型
項目怎麼分空間
上一課的設定都寫在容器上:排成一列、置中、換行。可是很多版面的問題是「每個項目該多寬」:
- 搜尋列:輸入框要占滿剩下的寬度,旁邊的按鈕維持原本大小。
- 三張卡片:不管內容多少,三張一樣寬。
- 手機畫面變窄:logo 不能被壓扁,其他項目可以縮。
這些都靠寫在項目上的三個屬性:
| 屬性 | 問的問題 | 預設值 |
|---|---|---|
flex-basis |
分配空間之前,這個項目原本想要多大? | auto(看 width 或內容) |
flex-grow |
還有剩餘空間時,我要分幾份? | 0(不長大) |
flex-shrink |
空間不夠時,我要讓出幾份? | 1(會縮小) |
預設值的意思是:項目照內容的大小排,不會自己長大去填滿空位,但空間不夠時會縮小。
用數字算:剩餘空間怎麼分
瀏覽器分配空間的步驟(以單行、橫向為例):
- 每個項目先拿到基準尺寸(flex-basis)。
- 算剩餘空間:容器寬度 − 所有基準尺寸 − 所有 gap。
- 剩餘空間是正的:依
flex-grow的比例分給項目。 - 剩餘空間是負的(放不下):依
flex-shrink × 基準尺寸的比例從項目身上扣掉。
例一:長大。 容器 600px,三個項目 flex-basis: 100px,flex-grow 分別是 1、2、0,沒有 gap。
| 項目 1 | 項目 2 | 項目 3 | |
|---|---|---|---|
| 基準尺寸 | 100 | 100 | 100 |
| 剩餘空間 600 − 300 = 300,按 1:2:0 分 | +100 | +200 | +0 |
| 最後寬度 | 200 | 300 | 100 |
例二:縮小。 容器 300px,兩個項目的基準尺寸是 300px 與 100px,flex-shrink 都是 1。
放不下的部分是 300 + 100 − 300 = 100px,依 1 × 300:1 × 100 = 3:1 扣掉,大的項目多讓一點:
| 項目 1 | 項目 2 | |
|---|---|---|
| 基準尺寸 | 300 | 100 |
| 要扣掉的 100px,按 3:1 分 | −75 | −25 |
| 最後寬度 | 225 | 75 |
flex-shrink: 0 表示「我不縮」:logo、頭像這類不能變形的東西常這樣設。
另外,項目預設不會縮到比內容最窄的寬度還窄(例如一個長單字),這是 min-width: auto 的作用;需要時可以設 min-width: 0 解除。
下面的實驗會把這張表算給你看,並和瀏覽器實際排出來的寬度並列。
簡寫 flex
三個屬性通常用簡寫 flex 一次寫完,順序是 grow shrink basis:
| 寫法 | 等於 | 意思 |
|---|---|---|
| 不寫(初始值) | 0 1 auto |
照內容大小,不長大,會縮小 |
flex: 1 |
1 1 0% |
基準尺寸當作 0,空間全部按比例分:幾個 flex: 1 就幾等分 |
flex: 2 |
2 1 0% |
分到的是 flex: 1 的兩倍 |
flex: auto |
1 1 auto |
先照內容大小,剩下的再平均分(內容多的最後還是比較寬) |
flex: none |
0 0 auto |
完全不伸縮,維持內容大小 |
flex: 1 和 flex: auto 的差別就在基準尺寸:三張卡片想要一樣寬,用 flex: 1;
想要「內容多的寬一點,但一起填滿」,用 flex: auto。
調整單一項目的兩個工具:
align-self:只改這一個項目在交叉軸上的對齊,覆寫容器的align-items。margin-left: auto:auto 外距會吃掉主軸上所有的剩餘空間,於是把這個項目(和它後面的項目)推到最右邊。導覽列「左邊 logo、右邊登入鈕」就是這樣做的。
小測驗:分配空間
容器寬 600px、沒有 gap,三個項目都是 flex-basis: 100px,flex-grow 依序是 1、1、2。第三個項目最後多寬?
flex: 1 等於下面哪一個寫法?
容器寬 300px,兩個項目的基準尺寸是 300px 和 100px,flex-shrink 都是 1(內容都很短)。最後的寬度是?
order:畫面順序和閱讀順序
order 讓項目在畫面上換位置:所有項目預設 order: 0,數字小的排前面,相同的照 HTML 的順序。
但它只改變視覺順序。下面這些都仍然照 HTML 原始碼的順序:
- 按 Tab 鍵時,鍵盤焦點移動的順序
- 螢幕閱讀器唸出內容的順序
- 選取、複製文字的順序
如果畫面上「看起來的順序」和「唸出來、Tab 過去的順序」不一樣,用鍵盤或螢幕閱讀器的人會覺得頁面跳來跳去。 無障礙規範 WCAG 的成功準則 1.3.2「有意義的順序」(Meaningful Sequence)就要求:內容的先後順序會影響意思時,程式能判斷出正確的閱讀順序。
所以實務上:
- 內容的真實順序,直接在 HTML 裡排對。
order只用在不影響意思的小調整(例如手機版把裝飾用的圖片移到文字後面)。row-reverse、column-reverse也有同樣的問題。
兩個常見版型:等高卡片與頁尾貼底
等高卡片列:容器 display: flex 之後,項目預設 align-items: stretch,三張卡片本來就一樣高。
麻煩的是卡片裡面:內容短的卡片,「報名」按鈕會停在中間。解法是讓卡片本身也變成直向的 flex 容器,按鈕用 margin-top: auto 推到底部:
.cards { display: flex; gap: 12px; }
.card { flex: 1; display: flex; flex-direction: column; } /* 卡片:等寬、內部直向排列 */
.card a { margin-top: auto; align-self: flex-start; } /* 按鈕:推到底部、不要被拉滿整個寬度 */
一個元素可以同時是 flex 項目(對外)和 flex 容器(對內),這是 Flexbox 版面的常見寫法。
頁尾貼底(sticky footer):內容很少時,頁尾不要浮在畫面中間,而是貼在視窗底部:
body { min-height: 100vh; display: flex; flex-direction: column; margin: 0; }
main { flex: 1; } /* 主內容吃掉剩下的高度,把頁尾推到底 */
100vh 是視窗高度的 100%。內容多到超過一個畫面時,頁尾就自然排在內容後面,不會蓋住內容。
小測驗:調整單一項目與常見版型
一列三個連結,第三個設定 order: -1 之後顯示在最前面。這時按 Tab 鍵,焦點的順序是?
導覽列裡有 logo、三個連結和一個「登入」按鈕。想讓「登入」貼到最右邊、其他維持在左邊,最直接的做法是?
容器設定 align-items: center,但其中一張卡片想貼齊上緣。這張卡片要設定什麼?
重點整理
flex-basis是基準尺寸;剩餘空間 = 容器 − 基準尺寸總和 − gap。- 有剩:依
flex-grow的比例分;不夠:依flex-shrink × 基準尺寸的比例扣,flex-shrink: 0表示不縮。 flex: 1=1 1 0%(幾個就幾等分);flex: auto=1 1 auto;flex: none=0 0 auto。margin-left: auto把項目推到最右邊;align-self調整單一項目的交叉軸對齊。order只改視覺順序,鍵盤與螢幕閱讀器仍照 HTML 順序;真實順序直接在 HTML 排好。- 常見版型:導覽列(flex+margin-left: auto)、等高卡片(flex: 1+卡片內直向 flex)、頁尾貼底(body 直向 flex+main flex: 1)。
下一課換另一種版面工具 Grid:Flexbox 一次管一個方向,Grid 同時管欄與列,適合排成格子的版面。