
結論先講
position 五個值,每個解決不同問題:
| 值 | 用途 | 關鍵 |
|---|---|---|
static | 預設,照文件流 | 不吃 top/left/right/bottom |
relative | 相對自己原位置偏移 | 主要當「定位參考點」給 absolute 子層 |
absolute | 脫離文件流,相對最近 positioned 祖先 | 沒有 positioned 祖先 → 相對 <html> |
fixed | 相對viewport 定位 | 但會被 transform 容器打破 |
sticky | 捲到某位置「卡住」 | 需要捲動容器跟 top/bottom 值 |
搞懂 absolute 找誰當父層跟 fixed 被 transform 打破這兩個坑,就解決 90% 的 position bug。
static:預設值
每個元素一開始都是 position: static。意思是:
- 照文件流排版
top/left/right/bottom/z-index都不吃margin/padding有效
99% 的元素該保持 static。
relative:相對自己偏移
.box {
position: relative;
top: 10px;
left: 20px;
}這個 box 在視覺上往下移 10px、往右 20px,但它原本占的空間還留著(其他元素位置不變)。
真正的用途:不是拿來偏移
position: relative 的真正工作是當 absolute 子層的定位參考點:
.card {
position: relative; /* 讓內部 absolute 以我為準 */
}
.card .badge {
position: absolute;
top: 8px;
right: 8px;
}沒有 position: relative,.badge 會漂到整個頁面右上角。
absolute:脫離文件流
.modal {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
}行為:
- 脫離文件流(不占空間,其他元素當它不存在)
- 相對「最近 positioned 祖先」定位
- Positioned =
position值不是static的元素 - 沒有 → 相對
<html>(根元素)
- Positioned =
最常踩的坑:找錯父層
<section class="wrapper"> <!-- 沒設 position -->
<div class="card"> <!-- 沒設 position -->
<span class="tag"></span> <!-- position: absolute -->
</div>
</section>.tag 以為會定位在 .card 或 .wrapper,實際相對 <html>。
修法:在你要的祖先元素加 position: relative:
.card { position: relative; }實戰用法
- Badge / Tag 疊在 card 角落
- 自訂 dropdown 選單
- Tooltip 浮層
- Modal / Dialog(搭配
position: fixed或 absolute over backdrop)
fixed:相對 viewport
.floating-button {
position: fixed;
bottom: 24px;
right: 24px;
}行為:
- 脫離文件流
- 相對瀏覽器 viewport 定位
- 捲動時不動
- 手機開鍵盤時 viewport 縮小,
fixed會跟著
最大的坑:transform 打破 fixed
CSS 有個規則:祖先元素有 transform、filter、will-change、perspective 時,fixed 會改成相對「那個祖先」而不是 viewport。
.page-wrapper {
transform: translate(0); /* 有 transform */
}
.page-wrapper .floating-btn {
position: fixed; /* 壞了!相對 .page-wrapper 定位 */
}這個坑在動畫庫(GSAP、Framer Motion)或想做 smooth scrolling 時很常撞到。
修法:
- 把
floating-btn搬出transform容器(放<body>底下) - 或者改用
position: sticky(另一個問題)
實戰用法
- 固定頁首 / 頁尾
- 浮動聊天按鈕
- 回到頂部按鈕
- Modal backdrop(配合
z-index: 999)
sticky:捲到某位置卡住
.table-header {
position: sticky;
top: 0;
background: white;
}行為:
- 在捲動容器內表現先像
relative - 當
top: 0被觸發(捲到該位置),切換成「卡住」 - 捲出捲動容器範圍,回到
relative
心智模型
想像一張便利貼,捲動時它會卡在容器頂端,直到容器整個卷過去。
sticky 不生效的 N 個原因
❌ 沒設 top 或 bottom:
.el { position: sticky; } /* 沒有 top,sticky 完全無效 */❌ 父層有 overflow: hidden / auto / scroll:
sticky 的「捲動容器」會是父層。若父層沒自己捲(overflow: visible),sticky 元素可能永遠看不到卡住效果。
❌ 父層高度不夠:
.parent { height: 100px; } /* 比 sticky 元素還矮,根本沒空間捲 */❌ 有 transform 祖先(跟 fixed 同問題)
實戰用法
- Table header 捲動保留
- Sidebar 捲到頂固定
- 長文章 TOC 側欄
z-index 堆疊順序
z-index 只對 positioned 元素(position 非 static)有效。
Stacking Context
不是單一 z-index 數字戰。會形成 stacking context(堆疊脈絡)。
觸發 stacking context 的條件:
position+z-index非 autoopacity < 1transform非nonefilter、backdrop-filter非noneisolation: isolatewill-change: transform / opacity
一個 stacking context 裡的 z-index 互相比大小,但整個 context 的 z-index 受外層 context 限制。
常見坑
<div style="opacity: 0.99; z-index: 1;">
<!-- 這裡 z-index: 9999 也贏不了外面 z-index: 2 -->
<div style="z-index: 9999;">我上不去</div>
</div>
<div style="z-index: 2;">我贏</div>因為 opacity: 0.99 建立了 stacking context,內部 z-index 被鎖在這個 context 裡。
Debug 工具:Chrome DevTools 有 “Layers” 面板可以看堆疊結構。
實戰 Pattern
Pattern 1:Modal 遮罩 + 內容
.backdrop {
position: fixed;
inset: 0; /* = top: 0; right: 0; bottom: 0; left: 0; */
background: rgba(0,0,0,0.5);
z-index: 999;
}
.modal {
position: fixed;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
z-index: 1000;
}Pattern 2:卡片 + 角落 badge
.card { position: relative; }
.card .badge {
position: absolute;
top: -8px; right: -8px;
}Pattern 3:Sticky TOC
.layout { display: grid; grid-template-columns: 1fr 200px; }
.toc {
position: sticky;
top: 80px; /* 扣掉 header 高度 */
align-self: start; /* Grid 下必要,不然 stretch 後 sticky 沒用 */
}Pattern 4:Header fixed + 內容避開
.header { position: fixed; top: 0; left: 0; right: 0; height: 60px; }
main { padding-top: 60px; } /* 空出 header 空間 */實戰 Checklist
- 想偏移元素但保留空間 →
relative - 需要浮在內容之上 →
absolute(父層要position: relative) - 需要相對 viewport →
fixed(小心 transform 祖先) - 要捲到某位置卡住 →
sticky(要top+ 父層不是 overflow:hidden) -
z-index衝突時看 stacking context(opacity、transform 都會建立) - Modal 用
inset: 0(簡潔)+z-index夠高
