2024 年起,align-content 終於可以在塊級佈局裡運作了。在這之前,想單靠一個屬性垂直置中是辦不到的,你得先把容器換成 Flexbox 或 Grid。

1
2
3
<div style="align-content: center; height: 100px;">
<code>align-content</code> 現在可以直接使用了!
</div>

注意上面那個 height: 100px,它不是裝飾。align-content 做的事情是「把內容在 block 軸上的剩餘空間裡分配」,前提是容器得真的有剩餘空間。普通的塊級元素高度是 auto,會被內容撐到剛剛好、一點空間都不剩,這時候寫 align-content: center 不會有任何視覺變化。要看到效果,容器至少要有明確的 heightmin-height,或是被外層(例如 grid track、flex item)給定一個比內容更大的高度。這個前提我第一次踩到時找了快二十分鐘,明明屬性寫對了卻沒反應。

瀏覽器支援狀況:Chrome 123、Firefox 125、Safari 17.4,都是 2024 年第二季才到位。覆蓋率還沒到 100%,生產環境得做漸進式增強——先讓舊瀏覽器也能正常置中,再讓新瀏覽器走更乾淨的寫法。後面會講到這裡有個陷阱,多數人以為的 @supports 寫法其實是錯的。

有什麼新變化?

過去在塊級佈局(flow)裡寫 align-content 是沒效果的,你得切換到 flexbox 或 grid。2024 年瀏覽器替流式佈局實作了 align-content,帶來兩個好處:

  • 不必切換佈局,一個屬性直接生效。
  • 內容不必再為了置中多包一層 div

順帶說一個老陷阱。單行 flexbox(預設 nowrap)裡,align-content 完全沒效果——整行已經撐滿交叉軸,沒有多餘空間可以分配:

1
2
3
4
<!-- 沒效果:單行 flexbox 的 align-content 不起作用 -->
<div style="display: flex; align-content: center; height: 100px;">
內容。
</div>

很多教學會教你「改成多行 flexbox(flex-flow: row wrap)讓行不再撐滿,align-content 就有事可做」。這招技術上行得通,但如果你的目的只是垂直置中,它其實是繞遠路:wrap 會改變整個容器的換行行為,多項目的 flex 容器一旦允許換行,排版可能整個跑掉。真正想置中,直接用 align-items 就好——align-items 控制的是子項目在交叉軸上的對齊位置,在單行 flexbox 下就生效,不需要動換行:

1
2
3
4
<!-- 想置中就用這個:align-items 在單行 flexbox 直接生效 -->
<div style="display: flex; align-items: center; height: 100px;">
內容。
</div>

當然,如果你確實需要多行排版又要整組行置中,那 align-contentwrap 才是對的工具,看需求選。

2024 年起,連佈局都不用換,流式佈局裡直接寫就行(記得容器要有高度):

1
2
3
4
<!-- 不需要切換佈局、也不需要額外包裝器 -->
<div style="align-content: center; height: 100px;">
包含<em>多個</em>節點的內容。
</div>

折騰幾十年,CSS 終於能用一個屬性搞定垂直對齊。

垂直置中:歷史回顧

對齊這種基本需求,CSS 長期以來就是沒有簡單解法,這點蠻反直覺的。以下是從古至今的垂直置中寫法(水平置中是另一個故事)。每一種我都附上容器高度,因為少了高度多半看不出效果。

方法 1:表格單元格

CSS 有 4 種主要佈局:flow(預設)、table、flexbox、grid。flexbox 和 grid 是後來才加的,所以最早能用的選擇是 table。

1
2
3
4
5
<div style="display: table; height: 100px;">
<div style="display: table-cell; vertical-align: middle;">
內容。
</div>
</div>

純靠 CSS 召喚出一張表格來置中,能用,但有點迂迴。

方法 2:絕對定位

1
2
3
4
5
<div style="position: relative; height: 100px;">
<div style="position: absolute; top: 50%; transform: translateY(-50%);">
內容。
</div>
</div>

流式佈局幫不上忙,所以繞過去:

  1. position: relative 標記參考容器。
  2. position: absolute; top: 50% 把內容上緣移到容器中心。
  3. transform: translateY(-50%) 把內容往上拉回自身高度的一半。

top: 50% 是相對父容器算的,translateY(-50%) 是相對元素自身算的,兩個百分比參照不同,配合起來剛好置中。這個方法有兩個限制要記牢:

  • 父容器必須有定位上下文position: relative/absolute/fixed),否則 position: absolute 的子元素會往上找到更遠的祖先當參照。
  • 子元素脫離了正常流,不再撐起父容器。如果父容器高度是 auto、本來靠這個子元素撐高,現在會直接塌成 0 高度,top: 50% 的 50% 就變成 0 的一半,置中也跟著失效。所以這招幾乎都得搭配父容器明確給高度。

用的人越來越少了,但相容性要求高的舊專案還是會碰到。

方法 3:行內內容

流式佈局不能做內容對齊,但可以在行內垂直對齊。那就讓一個行內元素和容器一樣高:

1
2
3
4
5
<!-- ::before 是 CSS 偽元素,不是真實的 HTML 節點;
實際 DOM 裡只有 .content 這一個子元素 -->
<div class="container">
<div class="content">內容。</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
.container {
height: 100px;
}
.container::before {
content: '';
height: 100%;
display: inline-block;
vertical-align: middle;
}
.content {
display: inline-block;
vertical-align: middle;
}

這個方法有個小地雷:除了犧牲一個偽元素,因為 .contentinline-block,兩個元素之間的原始碼換行會被當成一個空白字元,可能造成意外的間距。處理方式是把標籤之間的空白擠掉,或把父容器 font-size 設成 0。

方法 4:單行 flexbox

flexbox 在 Web 問世 20 年後才廣泛可用。單行模式(預設)下,整行填滿交叉軸,用 align-items 在交叉軸對齊:

1
2
3
<div style="display: flex; align-items: center; height: 100px;">
<div>內容。</div>
</div>

或者把主軸轉成縱向,改用 justify-content

1
2
3
<div style="display: flex; flex-flow: column; justify-content: center; height: 100px;">
<div>內容。</div>
</div>

方法 5:多行 flexbox(不推薦)

多行模式下,行不再填滿交叉軸,這時 align-content 才有剩餘空間可以分配,連只有一行也能置中:

1
2
3
<div style="display: flex; flex-flow: row wrap; align-content: center; height: 100px;">
<div>內容。</div>
</div>

這是技術上行得通的路,但不建議用它來做垂直置中:flex-wrap: wrap 會改變容器的換行行為,多項目時排版可能整個跑掉。如果目的只是垂直置中,方法 4 的 align-items 是更直接的選擇。

方法 6:網格內容對齊

grid 出現得更晚,對齊變得直接:

1
2
3
<div style="display: grid; align-content: center; height: 100px;">
<div>內容。</div>
</div>

方法 7:網格項目對齊

和前一種有個容易搞混的差異,這裡得把術語講準,不然會誤解 grid 的運作:

  • align-content:對齊的是整組網格軌道(grid tracks)相對於容器的位置。只有當所有軌道的總高度小於容器時才看得出效果。
  • align-items:對齊的是網格項目(grid item)在自己所屬網格區域(grid area)內的位置。預設項目會被拉伸填滿所屬區域,設成 center 才會縮回內容尺寸並置中。
1
2
3
<div style="display: grid; align-items: center; height: 100px;">
<div>內容。</div>
</div>

簡單記:align-content 動的是「軌道在容器裡的位置」,align-items 動的是「項目在自己格子裡的位置」。

方法 8:自動外邊距

流式佈局裡 margin: auto 只能水平置中,不能垂直。flexbox 和 grid 沒有這個限制:

1
2
3
4
5
<div style="display: grid; height: 100px;">
<div style="margin-block: auto;">
內容。
</div>
</div>

用邊距控制對齊,每次看到還是覺得奇妙。

方法 9:2024 年的寫法

繞了一大圈,回到一個屬性:

1
2
3
<div style="align-content: center; height: 100px;">
<code>align-content</code> 現在可以直接使用了!
</div>

方法 1 的表格單元格和這個方法一樣,都不需要內容包裝器(雖然 table 需要一層表格容器)。兜了一圈,回到起點。

接下來是最容易寫錯的部分:相容性處理。

漸進式增強的真正寫法

你可能想用 JavaScript 偵測支援度,這條路一開始就堵死:

1
2
3
4
5
// ⚠️ 這個偵測沒用
if ('alignContent' in document.documentElement.style) {
// 支援 flexbox/grid 的舊瀏覽器也會進來
// 因為 alignContent 屬性早就存在,只是對 block context 無效
}

align-content 這個屬性名稱長期以來都在(flexbox/grid 早就支援),所以 JavaScript 的 in 運算子完全分不出「支援 block context」和「只支援 flexbox/grid」。

@supports 也救不了你,而且這是大坑——很多人以為可以這樣寫:

1
2
3
4
5
6
7
8
9
10
11
/* ⚠️ 這段是錯的,別照抄 */
.container {
display: grid;
align-content: center;
}

@supports (align-content: center) {
.container {
display: block;
}
}

問題出在 @supports (align-content: center) 測的是「這個屬性值合不合法」,而 align-content: center 在 flexbox/grid 時代就是合法值了。結果是:所有支援 flex/grid 的瀏覽器都會回傳 true,包含那些只支援 flex/grid align-content、還不支援 block context 的舊瀏覽器。這些舊瀏覽器一進到 @supports 區塊就被改成 display: block,它們又不懂 block 的 align-content,於是 grid 的保底直接被蓋掉,置中失效。等於這條 @supports 把唯一能用的 fallback 親手砸了。Chrome 官方那篇支援說明也直接點明:block context 的 align-content 沒辦法用 feature query 偵測,feature query 永遠回傳 true。

既然沒有可靠的特性偵測,正確做法就是別去偵測它。把能在所有目標瀏覽器運作的版本當作唯一基底,不要用 @supports 去翻轉:

1
2
3
4
5
6
/* 唯一基底:grid,新舊瀏覽器都吃得下、都能正確置中 */
.container {
display: grid;
align-content: center;
height: 100px;
}

display: grid + align-content: center 在 2017 年左右就全面可用,等於拿一個老牌、覆蓋率近乎滿分的寫法當保底,視覺結果跟 block context 版本完全一樣。少了「block context 不用切換佈局」這個語意上的乾淨,但讀者拿到的是會動的程式碼,不是一段在舊瀏覽器悄悄爛掉的 @supports

如果你就是想要 block context 的純粹寫法,務實的判斷是:等到目標客群的瀏覽器覆蓋率夠了(看 caniuse 的數字決定),直接無條件寫 align-content: center 就好。在那之前,grid 保底是唯一不會自爆的選擇。

九種方法一次看

把前面九種寫法湊在一起,可以直接貼進 HTML 看效果(容器都給了高度才看得到差異):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<p>垂直置中的各種方法。黃色是容器,綠色是內容包裝器。注意第一種和最後一種不需要內容包裝器。</p>

<div class="container" style="display: table; height: 100px;">
<div style="display: table-cell; vertical-align: middle;">
表格單元格搭配 <code>vertical-align</code>
</div>
</div>

<div class="container" style="position: relative; height: 100px;">
<div class="content" style="position: absolute; top: 50%; transform: translateY(-50%);">
<b>絕對</b>定位。
</div>
</div>

<div class="container" style="height: 100px;">
<span style="display: inline-block; vertical-align: middle; height: 100%;"></span><div class="content" style="display: inline-block; vertical-align: middle;">
<b>行內</b>內容。
</div>
</div>

<div class="container" style="display: flex; align-items: center; height: 100px;">
<div class="content">單行 <b>flexbox</b>(交叉軸)。</div>
</div>

<div class="container" style="display: flex; flex-flow: column; justify-content: center; height: 100px;">
<div class="content">單行 <b>flexbox</b>(主軸)。</div>
</div>

<div class="container" style="display: flex; flex-flow: row wrap; align-content: center; height: 100px;">
<div class="content">多行 <b>flexbox</b>(交叉軸)。</div>
</div>

<div class="container" style="display: grid; align-content: center; height: 100px;">
<div class="content"><b>Grid</b> 軌道對齊。</div>
</div>

<div class="container" style="display: grid; align-items: center; height: 100px;">
<div class="content"><b>Grid</b> 項目對齊。</div>
</div>

<div class="container" style="display: grid; height: 100px;">
<div class="content" style="margin-block: auto;">
自動<b>外邊距</b>
</div>
</div>

<div class="container" style="align-content: center; height: 100px;">
<code>align-content</code> 在 2024 年直接生效!
</div>

二維對齊

有沒有對應的單一屬性處理水平對齊?看一下各對齊屬性在三種佈局的效果:

flow flexbox grid
align-content block 軸 交叉軸(行) block 軸(軌道)
justify-content 無效果 主軸 inline 軸(軌道)
align-items 無效果 交叉軸(項目) block 軸(項目於區域內)
justify-items 無效果 無效果 inline 軸(項目於區域內)

對應到 flow 佈局:align-content 管垂直(block 軸),但目前沒有對應的單一屬性能在 flow 裡管水平置中。水平方向還是得靠 margin: auto 或切換到 flex/grid。

背景:CSS 軸術語

block 軸通常是垂直的,inline 軸是水平的。會需要這組術語,是因為垂直 writing-mode 真的存在,所以 block 軸和 inline 軸是相對於文字方向定義的,不是寫死成上下左右。這就像 flexbox 的主軸和交叉軸是相對於排列方向,而不是固定方位。

至於 content 和 items 的命名差異:items 用在「可以各自獨立對齊」的東西上。flexbox 主軸方向的 flex 項目沒辦法各自分開對齊(它們共用同一條主軸的空間分配),所以那個方向用的是 content

小結

九種寫法繞了一大圈,2024 年的 align-content 確實把垂直置中收斂成一個屬性,但兩個前提得記住:容器要有可分配的 block 軸空間,而且 block context 的支援度沒辦法用 @supports 偵測。在覆蓋率追上之前,拿 display: grid + align-content: center 當無條件保底,是目前唯一不會在舊瀏覽器悄悄失效的寫法。