📋 目錄
如果你曾在 CSS 裡寫過
z-index: 9999,恭喜你,你已經參與了這場「層級軍備競賽」。但有沒有想過,為什麼我們需要用這麼大的數字?答案是:我們從一開始就錯了。
魔法數字的起源
打開任何一個有點歷史的專案,你很可能會看到這樣的程式碼:
.modal { z-index: 1000; }
.dropdown { z-index: 500; }
.toast { z-index: 9999; }
.sticky-header { z-index: 100; }
.tooltip { z-index: 2000; }
這些數字是怎麼來的?通常是開發者「試試看」之後發現「啊,這個不夠高,再調高一點」。然後下一個人遇到同樣的問題,於是寫下了 z-index: 10000。這就是所謂的「魔法數字」——沒有人知道為什麼是這個數字,但它就這樣存在著。
問題來了:
- 無法預測:新增一個元件時,你不知道該用什麼數字
- 衝突風險:不同元件可能使用相同的 z-index,導致層級錯亂
- 維護困難:要調整層級關係,必須一個一個改
- 無意義的數字:
9999和10000有什麼本質區別?沒有
這就是所謂的「軍備競賽」——誰寫的數字大,誰就贏了。但這種比賽沒有贏家,只有技術債。
Token 化:系統性的解決方案
解決方案很簡單:不要用隨機數字,用命名來代表層級。這就是所謂的「Token 化」——將 z-index 值抽象成有意義的名稱。
建立你的 z-index 系統
首先,在你的 CSS 根層級定義一套 z-index token:
:root {
/* 基礎層級 - 最底層 */
--z-base: 0;
/* 略高於內容 */
--z-above-content: 10;
/* 懸停效果、active 狀態 */
--z-hover: 50;
/* 固定導航列 */
--z-sticky: 100;
/* 下拉選單、浮動按鈕 */
--z-dropdown: 200;
/* 提示工具 */
--z-tooltip: 300;
/* 彈出式訊息(Toast) */
--z-toast: 400;
/* 對話框、Modal */
--z-modal: 500;
/* 確認視窗(Confirm Dialog) */
--z-confirm: 600;
/* 最頂層:載入中、全螢幕遮罩 */
--z-overlay: 1000;
}
這套系統的重點是什麼?數字之間有足夠的間距。這樣你可以在中間插入新的層級,而不需要重新調整整個系統。
使用方式
現在,當你需要設定 z-index 時,直接使用這些 token:
.navbar {
position: sticky;
z-index: var(--z-sticky);
}
.dropdown-menu {
position: absolute;
z-index: var(--z-dropdown);
}
.modal {
position: fixed;
z-index: var(--z-modal);
}
.loading-overlay {
position: fixed;
inset: 0;
z-index: var(--z-overlay);
}
這樣一來,層級關係一目了然。你不需要記住哪個元件應該用什麼數字——只要知道它的「語意」就可以了。
calc() 的妙用:相對層級
但有時候,你可能需要一些「動態」的層級。比如說,一個 modal 裡面的 tooltip,應該比 modal 本身高,但比最頂層低。這時候 calc() 就派上用場了。
相對層級語法
.modal-tooltip {
/* modal 的層級 + 1 */
z-index: calc(var(--z-modal) + 1);
}
.modal-close-button {
/* modal 內部的關閉按鈕,稍微高一點 */
z-index: calc(var(--z-modal) + 1);
}
.confirm-dialog {
/* 比普通 modal 高,但比 overlay 低 */
z-index: calc(var(--z-overlay) - 100);
}
這種方式的優點是什麼?你可以建立「相對於某個基礎值」的層級,而不需要記住絕對數字。
進階:動態層級
如果你使用 JavaScript 動態建立元件(例如 React 的 Portal),你可能需要動態計算 z-index:
// 假設我們需要一個 modal,其 z-index 基於當前頁面的 modal 數量
let modalCount = 0;
function openModal() {
const zIndex = 500 + modalCount; // 從 500 開始遞增
modalCount++;
return {
zIndex,
close: () => {
modalCount--;
}
};
}
但在大多數情況下,靜態的 token 系統就足夠了。動態計算通常是不必要的複雜。
實務建議:建立團隊共識
Token 化 z-index 不只是技術問題,更是團隊協作的問題。以下是一些實務建議:
1. 文件化你的層級系統
在你的專案文件或 CSS 開頭註解中,說明每個層級的用途:
/*
* z-index 層級系統
*
* 0-99: 基礎內容層(預設)
* 100-199: 導航與固定元素
* 200-299: 浮動與互動元素
* 300-399: 提示與小型彈出
* 400-499: 重要通知
* 500-599: 主要對話框
* 600-699: 高優先權對話框
* 1000+: 全螢幕覆蓋層
*/
2. 避免「層級蔓延」
有些團隊會忍不住一直增加新的層級:「這個要在 modal 上面」「那個要在 confirm 上面」。時間久了,你的 z-index 系統會變得跟原本的魔法數字一樣混亂。
解決方法是:嚴格限制層級數量。只有當真的有需要跨越多個層級的元件時,才考慮新增。
3. 考慮使用 CSS @layer
CSS 的 @layer 為你提供了另一個管理層級的工具:
@layer base {
.content { z-index: 1; }
}
@layer components {
.card { z-index: 10; }
}
@layer overlays {
.modal { z-index: 100; }
}
@layer 的好處是:即使你在不同 layer 中定義了相同的 z-index,後面的 layer 會自動覆蓋前面的。但這是一個更進階的主題,需要另外詳細介紹。
總結
告別 z-index: 9999 的時代吧!透過系統性的 Token 管理,你可以:
- 提升可讀性:層級關係一目了然
- 降低維護成本:新增、調整元件不再需要東改西改
- 減少衝突:明確的命名避免意外的層級覆蓋
- 團隊協作:統一的命名讓程式碼更容易理解
z-index 不應該是一場軍備競賽。它應該是一個有紀律的系統。
從今天開始,讓你的 CSS 變數成為你的層級管理幫手。
你有自己管理 z-index 的方法嗎?歡迎在 comments 分享你的經驗!