cover

結論先講

position 五個值,每個解決不同問題:

用途關鍵
static預設,照文件流不吃 top/left/right/bottom
relative相對自己原位置偏移主要當「定位參考點」給 absolute 子層
absolute脫離文件流,相對最近 positioned 祖先沒有 positioned 祖先 → 相對 <html>
fixed相對viewport 定位但會被 transform 容器打破
sticky捲到某位置「卡住」需要捲動容器跟 top/bottom

搞懂 absolute 找誰當父層跟 fixedtransform 打破這兩個坑,就解決 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>(根元素)

最常踩的坑:找錯父層

<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 有個規則:祖先元素有 transformfilterwill-changeperspective 時,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 個原因

❌ 沒設 topbottom

.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 元素positionstatic)有效。

Stacking Context

不是單一 z-index 數字戰。會形成 stacking context(堆疊脈絡)

觸發 stacking context 的條件:

  • position + z-index 非 auto
  • opacity < 1
  • transformnone
  • filterbackdrop-filternone
  • isolation: isolate
  • will-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 夠高

相關文章