你寫了 display: flex,瀏覽器偷偷塞了五個值給你
你大概常寫這種程式碼:把三個 div 包在一個 container 裡,加一句 display: flex,東西就乖乖橫向排好了。
<div class="container">
<div class="box">A</div>
<div class="box">B</div>
<div class="box">C</div>
</div>.container {
display: flex;
}順利的話畫面長得跟你期待的差不多,於是你心想「好,搞定」就跳下一個任務了。
但其實,你寫了一行 CSS,瀏覽器幫你寫了一堆。今天把這些「藏起來的預設值」挖出來看清楚 —— 特別是大家最常搞混的 flex-grow、flex-shrink、flex-basis 三個傢伙。
等等,那三個 box 到底為什麼是那個寬度?
做個實驗。給 container 一個明確寬度,三個 box 都不設寬度:
.container {
display: flex;
width: 600px;
border: 2px solid black;
}
.box {
background: lightblue;
/* 注意:完全沒設 width */
}結果你會看到:每個 box 的寬度剛好等於它內容的寬度 —— 字多就比較寬,字少就比較窄。container 剩下的空間?空在那邊沒人要。
奇怪吧?你以為 flex 會自動分配空間,但好像不是?
這是因為瀏覽器其實偷偷給每個 flex item 套了這行 default:
.box {
flex: 0 1 auto;
}翻成白話:「不主動長大、空間不夠的時候願意縮、預設大小看內容自己決定。」
flex 是一個 shorthand(縮寫屬性),它一次寫三個值,順序固定是:
flex: <flex-grow> <flex-shrink> <flex-basis>如果你跟我一樣第一次看到這三個名字就頭痛 —— 別急,我們用一個更好的方式來記它。
把它想成一份「彈性合約」
flex-grow、flex-shrink、flex-basis 這三個名字其實不太直觀。我習慣這樣重新框架它們:
flex: [想長多大] [願意縮多少] [理想起點]💡 記憶錨點:把
flex: A B C想成flex: [max 行為] [min 行為] [起點]
- A 越大,越會搶剩下來的空間
- B 越大,越會在擠的時候讓步
- C 是「如果空間剛剛好,我想要這個大小」
換句話說,這三個值在描述一個 flex item「面對空間變化時的態度」:
- 空間有剩 → 看 A(grow)來決定怎麼分
- 空間不夠 → 看 B(shrink)來決定誰要縮
- 空間剛好 → 大家就維持 C(basis)的大小
OK,有了這個框架,我們一個一個拆開。
flex-grow:「剩下的空間給我多少?」
flex-grow 預設是 0,意思是「就算有剩,我也不主動搶」。
這就是為什麼前面那個例子,三個 box 不會撐滿 container —— 因為它們都是 flex-grow: 0,沒人想要那塊多出來的空間。
來改一下,把第一個 box 設成 flex-grow: 1:
.box:nth-child(1) {
flex-grow: 1;
}現在第一個 box 會把所有剩下的空間吃光,另外兩個維持原本內容寬度。
那如果三個都設 flex-grow: 1 呢?
.box {
flex-grow: 1;
}直覺以為是「每個一樣寬」對吧?
但這裡有個雷:它們不一定等寬。flex-grow 分的是「剩下來的空間」,不是「整個 container 的空間」。如果 box A 的內容是 Hi,box B 的內容是一整段長文,B 一開始就比較寬 —— 即使你給三個都 flex-grow: 1,B 還是會比較寬,只是「剩餘空間」會被平均分掉而已。
要真的等寬,必須把 flex-basis 一起改。等下會講。
不同數字代表的是「比例」
如果你寫:
.box:nth-child(1) { flex-grow: 1; }
.box:nth-child(2) { flex-grow: 2; }
.box:nth-child(3) { flex-grow: 1; }意思是「剩下的空間切成 4 份(1+2+1),第一個拿 1 份、第二個拿 2 份、第三個拿 1 份」。
注意:這個分配是針對「剩下」的空間,不是整個 container。所以最終寬度不會剛好是 1:2:1。這真的很容易搞混,我自己一開始也卡了好久。
flex-shrink:「擠的時候我願意縮多少?」
flex-shrink 預設是 1,意思是「空間不夠的時候,跟大家一起縮」。
來看一個會擠的場景:
.container {
display: flex;
width: 300px;
}
.box {
width: 200px; /* 三個加起來 600px,明顯放不下 */
}照理說 200 × 3 = 600,比 container 大兩倍。但你不會看到 box 滿出來 —— 因為 flex-shrink: 1 預設讓它們等比例縮小,最後剛好塞進 300px。
那如果我說「我不要縮」呢?
.box:nth-child(1) {
flex-shrink: 0; /* 我硬是不縮 */
}第一個 box 維持 200px,剩下 100px 由另外兩個 box 去分(它們得縮得更兇才塞得下)。
💡 實際用途:
flex-shrink: 0在做側邊欄、logo、icon、固定寬度的按鈕時很有用 —— 你不想讓它們在小螢幕被擠扁。
為什麼 grow 預設 0,但 shrink 預設 1?
老實說,這個不對稱一開始很容易搞混。但仔細想想其實合理:
- 擠不下是嚴重問題,必須有人縮,不然會破版 → 所以預設大家都願意縮(shrink: 1)
- 空間有剩不是問題,留白也沒關係 → 所以預設沒人主動搶(grow: 0)
換句話說,瀏覽器的預設立場是:「擠的時候大家一起忍耐,剩的時候就留著吧。」
flex-basis:「如果一切剛好,我想要多大?」
flex-basis 是三個裡面最微妙的一個。它是 flex item 在「分配剩餘空間或縮減之前」的起始尺寸。
預設值是 auto,意思是「看我自己的內容決定」。
來比較三種寫法,假設 container 寬 600px、三個 box 內容寬度不一樣:
/* 寫法 A:看內容 */
.box { flex-basis: auto; }
/* 寫法 B:固定起點 */
.box { flex-basis: 100px; }
/* 寫法 C:從零開始 */
.box { flex-basis: 0; }差異整理如下:
| flex-basis | 結果 |
|---|---|
auto | 每個 box 起點是自己的內容寬度,剩餘空間照 grow 分 |
100px | 每個 box 從 100px 開始,剩下 300px 照 grow 分 |
0 | 每個 box 從 0 開始,整個 600px 照 grow 分 |
這就解釋了為什麼前面說「三個都 flex-grow: 1 不一定等寬」。要等寬,把 basis 設成 0:
.box {
flex: 1 1 0; /* grow=1, shrink=1, basis=0 */
}或最常見的縮寫:
.box {
flex: 1; /* 等同 flex: 1 1 0 */
}等等,這裡藏了一個雷。下一節說。
但是!flex: 1 跟你以為的不一樣
flex 這個 shorthand 有幾個很容易搞混的「速記值」:
.box { flex: 1; } /* = flex: 1 1 0 ⚠️ basis 是 0,不是 auto! */
.box { flex: auto; } /* = flex: 1 1 auto */
.box { flex: none; } /* = flex: 0 0 auto */
.box { flex: 0; } /* = flex: 0 1 0 ⚠️ */特別注意 flex: 1 跟 flex: auto 的差別 —— 一個 basis 是 0、一個是 auto。
flex: 1→ 不管內容多寡,大家平均分(因為起點都從 0 算)flex: auto→ 起點是內容大小,只有「剩下」的空間才平均分
90% 的人想要的是 flex: 1,但會直覺寫成 flex: auto,結果發現排版怪怪的。
但是!內容大小可以打破這一切
到目前為止我們都假設 flex item 會乖乖照 basis 走。但實際上有個隱藏規則:
flex item 預設不會縮到比它的內容還小。
舉例:
.container {
display: flex;
width: 300px;
}
.box {
flex: 1 1 0; /* 起點 0、可以縮可以長 */
}照理說,三個都從 0 起跳、grow 1、shrink 1,應該各 100px。但如果其中一個 box 裡有一個超長字串(例如一個沒有空格的 URL,像 https://example.com/very-long-path-without-spaces),那個 box 就會被「撐」到至少能放下那串文字的寬度 —— 即使 shrink 是 1 也壓不下去。
為什麼? 因為 flex item 預設還偷偷有一個屬性 min-width: auto,意思是「最小不能小於內容寬度」。
要解掉這個鎖,加這兩行:
.box {
min-width: 0; /* 允許縮到比內容還小 */
overflow: hidden; /* 配合處理溢出的部分 */
}這個 min-width: 0 是 flex 排版裡一個著名的「魔法咒語」。第一次遇到溢出問題的人通常會卡很久 —— 因為文件不會主動告訴你還有這個隱藏的 min-width 在搞鬼。老實說,這個設計挺反直覺的。
🐛 常見錯誤與除錯提示
常見錯誤 1:三個 box 都寫 flex-grow: 1 卻不等寬
會在什麼時候出現?
當你想做等寬的網格,給每個 flex item 設 flex-grow: 1,但發現內容多的 box 還是比較寬。
錯誤示範:
.box {
flex-grow: 1;
/* 沒設 flex-basis,預設是 auto */
}為什麼?
flex-grow 分的是「剩下」的空間,不是「全部」的空間。如果某個 box 內容比較長,它的初始寬度(auto = 內容寬度)就比較大,加上「平均分配的剩餘空間」之後,總寬度當然還是比較大。
修正:
.box {
flex: 1; /* = flex: 1 1 0,起點都從 0 算 */
}把 basis 設成 0,每個 box 從同一個起點開始,剩下的空間才能真正平均切。
常見錯誤 2:長 URL / 長字串把 flex item 撐爆,overflow 沒效果
會在什麼時候出現?
你在做聊天訊息泡泡或卡片,裡面可能出現很長的 URL。你預期超長文字會出 scroll bar 或被截斷,但它卻把整個 box 撐到比 container 還寬。
錯誤示範:
.container {
display: flex;
width: 400px;
}
.message {
flex: 1;
overflow: auto; /* 期待會出 scroll bar */
}錯誤現象:
沒有錯誤訊息,就只是看起來怪怪的 —— 整個 container 被內容撐開、把 layout 推爆,你的 overflow 完全沒被觸發。
為什麼會這樣?
因為 flex item 預設有 min-width: auto,最小寬度被綁住不能小於內容寬度。flex 排版會優先把 item 撐到放得下內容為止,根本沒機會輪到 overflow 生效。
修正:
.message {
flex: 1;
min-width: 0; /* 解掉隱藏的 min-width: auto */
overflow: auto;
}加上 min-width: 0,flex item 才會允許縮到比內容還小,overflow 才會真的生效。
常見錯誤 3:flex: 1 跟 flex: auto 搞混
會在什麼時候出現?
你想做平均分配,習慣性寫 flex: auto,結果發現有些 box 寬有些 box 窄。
錯誤示範:
.box {
flex: auto; /* 你以為這是平均分配 */
}為什麼?
flex: auto展開是flex: 1 1 auto(basis 是 auto,看內容)flex: 1展開是flex: 1 1 0(basis 是 0,從零起跳)
內容大小不同的時候,這兩個結果完全不一樣。
修正:
.box {
flex: 1; /* 真的想要平均分配的話用這個 */
}