UrsaKit 封面圖:用 CSS 把元素置中的指南

CSS 置中為什麼老是做不對?因為正確答案取決於父層,也因為 CSS 後來長出了三種都很好用的做法。這篇先給短答案,再依情境給方法,最後說明那些「在這裡可行、換個版面就默默失效」的陷阱。

文中每個範例都能在 UrsaKit 的 CSS 網上編輯器 上貼上就看到結果。

01短答案

/* 單一元素、兩個軸向,父層有高度 */
.parent {
  display: grid;
  place-items: center;
}

/* 多個子元素,單軸或雙軸 */
.parent {
  display: flex;
  justify-content: center;
  align-items: center;
}

/* 有寬度的區塊,只做水平置中 */
.child {
  width: min(100%, 40rem);
  margin-inline: auto;
}

這三種涵蓋了絕大多數情況。接下來解釋每一種各自依賴什麼。

02Flexbox:預設答案

Flexbox 沿著一個軸向分配剩餘空間,並在另一個軸向對齊項目。

.parent {
  display: flex;
  justify-content: center; /* 主軸:預設是水平 */
  align-items: center; /* 交叉軸:預設是垂直 */
  min-height: 100vh;
}

兩個細節決定它會不會成功:

  • 主軸跟著 flex-direction 走。 當 flex-direction: column 時,justify-content 控制垂直、align-items 控制水平。把這兩個屬性讀成「水平」與「垂直」,正是置中看起來壞掉的原因。
  • 容器必須有多餘空間。 align-items: center 分配的是子元素用完之後剩下的高度。如果父層高度等於內容高度,就沒有東西可分配,元素會留在原位。

想把整組內容一起置中,用 place-content: center。

justify-content 與 align-content 決定的是 flex 或 grid 容器裡「行」的位置。如果多個子元素應該聚在一起置中、而不是分散開來,就設 place-content: center,並讓 align-items 保持預設值。

03Grid:兩行解決兩個軸向

Grid 可以在完全不知道子元素大小的情況下,把它在兩個軸向置中。

.parent {
  display: grid;
  place-items: center;
  min-height: 100vh;
}

place-items: center 是 align-items: center 與 justify-items: center 的簡寫。它能成立,是因為子元素佔的是自己的 grid 區域,而不是共用的 flex 行。

這個簡寫以 Grid 為先。在 Flexbox 裡 justify-items 不生效,因為主軸上的項目是整組處理,所以 place-items 的第二個值會被忽略。

與 Flexbox 的實際差異,在多個子元素時最明顯:

版面FlexboxGrid
單一子元素置中可行最短
子元素排成一列、整組置中justify-content: centerplace-content: center
每個子元素在各自格子裡置中麻煩place-items: center
等高的欄位要注意 align-items: stretch原生支援

04margin: auto

對於寬度小於容器的區塊元素,auto 外距會分配剩下的空間:

.child {
  width: min(100%, 40rem);
  margin-inline: auto;
}

讓人意外的是垂直版本。在一般區塊版面中,垂直的 auto 外距會計算為 0,所以 margin: auto 無法把區塊垂直置中:

/* 不會垂直置中:父層不是 flex 或 grid 容器 */
.parent {
  height: 100vh;
}
.child {
  margin: auto;
}

/* 會垂直置中:子元素現在是 flex 項目 */
.parent {
  display: flex;
  height: 100vh;
}
.child {
  margin: auto;
}

這不只是冷知識。項目上的 margin: auto 正是下面那個溢出問題的標準解法。

05無法控制父層時,用絕對定位

如果父層必須維持普通區塊——canvas 外框、簡報、第三方函數庫渲染出來的標記——就把元素移出文件流:

.parent {
  position: relative;
}
.overlay {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 200px;
  height: 100px;
}

inset: 0 搭配 margin: auto 可以在不知道父層尺寸的情況下,置中一個固定大小的盒子。尺寸未知時,則改用 transform 版本:

.overlay {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

transform 做法會讓元素不再參與父層版面,所以它適合當備援,而不是預設做法。

06文字與行內內容

行內內容靠父層的 text-align 置中,而垂直置中是另一個問題:

.title {
  text-align: center;
}

.badge {
  display: grid;
  place-items: center;
  min-height: 2.5rem;
}

/* 舊式單行技巧:只對單行有效 */
.button {
  line-height: 2.5rem;
  height: 2.5rem;
}

line-height 置中在文字折成兩行的瞬間就破功,因為行框仍是一行的高度,內容卻長大了。優先用元素自己的 flex 或 grid。

07每種做法會失效的情況

做法失效時機修正
Flexbox,雙軸置中容器沒有多餘高度加 min-height 或給父層高度
Flexbox,雙軸置中置中的子元素比容器大在項目上用 margin: auto,或用 justify-content: safe center
flex-direction: column 下的 justify-content軸向期待被對調水平軸改用 align-items
margin: auto父層是一般區塊版面把父層改成 flex 或 grid
margin-inline: auto元素是 width: 100%設定最大寬度,例如 max-width: 40rem
絕對定位加 transform父層沒有定位在父層設 position: relative
line-height 置中文字換行改用 grid 或 flex

置中的 flex 元素可能把自己的上緣藏起來。

當內容比容器高、又套用了 justify-content: center(或 align-items: center),溢出部分會延伸到滾動原點之上,開頭幾行就再也捲不到。在項目上加上 margin: auto、或改用 safe center,就能讓內容開頭保持可滾動。

08除錯置中問題的順序

  1. 先看父層高度 — 在開發者工具的版面面板選取父層。如果它的高度等於內容高度,垂直置中就沒有材料可用。
  2. 確認父層用哪個軸向 — flex-direction: row 表示 justify-content 是水平。改成 column 卻沒改屬性,是「突然不置中了」最常見的原因。
  3. 找找有沒有 width: 100% — 已經填滿容器的元素沒有剩餘空間,auto 外距自然沒作用。
  4. 用真實內容測試,而不是空狀態 — 先用實際文字置中,再用溢出的文字測一次,在 CSS 網上編輯器 上做。多數置中 bug 只在內容變大時才出現。

要把這件事做對,關鍵多半在於搞清楚「剩餘空間屬於哪個元素」。這一點清楚之後,屬性名稱就不再神祕了。

09資料來源

常見問題

只置中一個元素,最短的寫法是什麼?

把父層設為 grid 容器並加上 place-items: center。元素會在水平與垂直方向同時置中,而且父層不需要知道子元素的大小。

為什麼 align-items: center 完全沒作用?

因為父層沒有多餘的高度可以分配。垂直置中需要父層有明確高度、min-height,或是一條被拉伸的 flex/grid 軸線。

為什麼 margin: auto 只能水平置中?

在一般區塊版面中,auto 外距只會分配行內方向剩下的空間。要靠 auto 外距垂直置中,元素必須是 flex 或 grid 項目,因為那時區塊軸也會分配剩餘空間。

為什麼置中的 flex 元素捲不到最上面?

當 flex 項目比容器大、又套用了 justify-content: center 時,溢出部分會跑到滾動原點之上。改在項目上使用 margin: auto,或用 safe center,就能保住起始端。

place-items 只能用在 Grid 嗎?

place-items 是 align-items 與 justify-items 的簡寫,兩者在 Grid 可用,在 Flexbox 只有部分生效。Flexbox 裡 justify-items 沒有作用,因為 flex 項目是由 justify-content 分配。

還需要用絕對定位加 transform 嗎?

只有在父層沒辦法變成 flex 或 grid 容器時才用,例如父層由第三方函數庫控制,或元素必須疊在 canvas 之上。

返回文章列表

相關文章

相關工具