CSS Position 完整指南:理解網頁元素定位的核心系統

Created: 2025/11/15
Updated: 2025/11/15

簡單記錄了關於CSS中的position


在前端開發中,position 是最常見、但也是最容易踩坑的 CSS 屬性之一。

常見的問題如下:

  • absolute 並沒有以你預期的父層作為定位基準
  • fixed 本來應該黏在螢幕上,卻跟著某個父層一起移動
  • sticky 在某些容器中突然失效
  • relative 到底會不會影響排版弄不清楚

這篇文章整理了 CSS 中常見定位模式的行為與原理,並解釋它們的基準(containing block)如何被計算,也補上 stacking context(疊放上下文)與 z-index 的實戰陷阱,幫助建立一個完整心智模型。


1. 關於Stacking Context和 Containing block

在我們討論position屬性之前,我們先釐清一下Stacking Context和Containing Block這兩個概念。

什麼是 Containing Block ?

CSS Display Module Level 4

簡單來說,它就是一個矩形框框,用來當作某個 box (例如:div 或其他元素)在算大小位置時的參考基準。

Containing Block 本身不是 box,它就是個矩形。但它通常是從某個元素的尺寸推算出來的。每個元素 (box) 定位時都會以它的 Containing Block 為基準。

這裡有一點要注意,元素不一定會被它限制住!它還是有可能溢出 (overflow) 這個 Containing Block 的範圍。

通常情況下,一個元素的邊界會為它的後代元素establish一個 Containing Block。所以我們會說,這個元素「為其後代建立了 Containing Block」。

當你在 CSS 裡面引用到 Containing Block 的屬性時,指的就是產生這個 Containing Block 的那個元素身上的屬性值。(如果是最一開始的 initial containing block,屬性值就是來自根元素 root element,除非有特別設定。)

什麼是 Stacking Context?

Visual formatting model

我們可以把 Stacking Context 想像成網頁繪製時的三維空間概念。除了我們常見的水平 (x 軸)垂直 (y 軸) 位置,元素還多了一個 「深度」或「前後」的 z 軸 位置!當Box們在視覺上重疊時,這個 z 軸 就超級重要了。

  • 網頁在 rendering tree 時,它的繪製順序就是用 Stacking Context 這個概念來描述的。
  • Stacking Context 可以一層包一層,裡面也可以再包含其他的 Stacking Context。
  • 外面的 Stacking Context 來看,裡面的這個 Stacking Context 就像個atomic 一樣,是個整體。其他 Stacking Context 裡面的box,絕對不能插隊或打亂內部box的繪製順序。

box的堆疊層級 (Stack Level)

  • 每個box都會屬於某個 Stacking Context。
  • 在同一個 Stacking Context 裡,所有有定位 (positioned) 的元素,都會有一個整數Stack Level,用來表示它在 z 軸上跟其他元素的相對位置。
    • Stack Level 值越大的元素,永遠會被畫在層級較小的元素前面
    • 元素也可能會有負值的 Stack Level。
    • 如果多個元素有相同的 Stack Level,那它們就會按照它們在 文件樹 (document tree) 裡面的出現順序,由後到前依序堆疊。

誰會建立 Stacking Context?

root element,也就是 <html> 標籤,會形成最底層的 Root Stacking Context

其他 Stacking Context 則是由任何具有定位 (positioned) 的元素(像是 position: relative),而且它被計算出來的 'z-index' 值不是 'auto' 時所產生的。

注意: Stacking Context 不一定會跟 Containing Block 有什麼關係。

繪製順序(從後到前)

每個 Stacking Context 裡面,元素的繪製順序是固定的,由最底層到最上層依序如下:

  1. 形成這個 Stacking Context 的元素的背景邊框
  2. 負值 Stack Level 的子 Stacking Context。(負值越小的越先畫)
  3. 屬於正常文件流 (in-flow)、 inline-level沒有定位 (non-positioned) 的後代元素。
  4. 沒有定位浮動元素 (non-positioned floats)。
  5. 屬於正常文件流 (in-flow)、inline-level沒有定位的後代元素。(包含 inline tablesinline blocks
  6. Stack Level 為 0 的子 Stacking Context,以及 Stack Level 為 0定位後代元素。
  7. Stack Level 為正值的子 Stacking Context。(從最小的正值開始畫)

總結來說: 對於第 6 層(Stack Level 為 0 的定位元素)、第 4 層(未定位浮動)、第 5 層(行內區塊和行內表格)的這些元素,它們的繪製方式會像它們自己產生了新的 Stacking Context 一樣,然後這個繪製順序就會不斷地遞迴應用到每一個 Stacking Context 裡面去。


簡單說明完了,什麼是Containg block和Stacking Context後,接下來介紹並說明position屬性。

2. 關於 position 屬性

CSS Positioned Layout Module Level 3

The position property determines which of the positioning schemes is used to calculate the position of a box. Values other than static make the box a positioned box, and cause it to establish an absolute positioning containing block for its descendants.

position 屬性決定了使用哪種定位方案來計算 box 的位置。

除了 static 以外的值,元素會成為 positioned box,並且為其子孫建立一個 absolute positioning containing block。


1.1 static

  • 該 box 不是 positioned box
  • 使用父層的 formatting context 規則進行布局。
  • top / right / bottom / left 等 inset 屬性不適用

1.2 relative

  • 元素會先依照一般(static)正常文流放好位置。
  • 再在這個基準位置上加上偏移量(top / left 等)。
  • 這個偏移是純視覺效果,且(除非另有指定)不會影響任何 非子孫元素 的大小或位置。
  • 但可能會讓祖先元素的 可捲動溢出區域(scrollable overflow area)變大

1.3 sticky

relative 幾乎相同,不同點在於:

inset 屬性(top/right/bottom/left 或對應的 inset-*)不是全部為 auto 的軸向上,元素的偏移會自動依據最近的 ancestor scroll container 的 scrollport(並受到 inset 屬性影響)調整,
以便在使用者捲動時,盡可能讓此盒子在其 containing block 內保持可見。


1.4 absolute

  • 元素會被 移出正常文檔流(out of flow)。不影響兄弟或祖先元素的尺寸或位置,也不參與父元素的 formatting context。
  • 位置與大小 完全依照其「絕對定位的 containing block」(並受 inset properties 修正)來決定。可與文流內的內容或其他絕對定位元素重疊。
  • 會被計入產生其 containing block 的元素的 可捲動溢出區(scrollable overflow area) 中。
  • 這種定位方式稱為 絕對定位(absolute positioning)

關於 scrollable overflow,規範中這樣描述:

box 的「scrollable overflow」是指超出該 box padding edge 的部分,需要為其提供滾動機制。
可捲動溢出區域(scrollable overflow area)是 scrollable overflow 所佔據的非矩形區域,而 scrollable overflow rectangle 則是軸線與盒子軸線對齊且包含可捲動溢出區域的最小矩形。——2.2. Scrollable Overflow

absolutecontaining block 如何計算?

可以用一句話簡潔地概括其基準:

元素會往外層尋找最近一個會建立 absolute containing block 的祖先元素。 如果未找到任何符合條件的祖先 \rightarrow 則以 initial containing block(通常等同於 viewport)為基準。

常見範例:

.parent {
  position: relative; /* 或 absolute / fixed / sticky */
}
 
.child {
  position: absolute;
  top: 0;
  left: 0;
}

在此情況下,.child 的 top / left 會以 .parent 元素的 padding box 邊界作為 (0, 0) 基準點。

(可參考 MDN:position


1.5 fixed

absolute 類似,但不同之處在於:

  • box 的定位與尺寸會相對於 fixed positioning containing block

    • 在 continuous media 中通常是 viewport
    • 在 paged media 中則是 page area
  • 元素相對於這個參考矩形是固定不動的:

    • 若基準是 viewport:捲動畫面時不會移動
    • 若基準是 page area:文件分頁時會在每一頁上重複出現

這種定位方式稱為 固定定位(fixed positioning)
並且被視為 絕對定位(absolute positioning)的子集

overflow-block 描述了當內容在 block 軸向溢出初始 containing block 時裝置的行為:

  • none:在 block 軸向上不提供任何溢出處理,溢出內容不顯示
  • scroll:在 block 軸向上,溢出內容可透過捲動顯示
  • paged:內容被切成數個獨立頁面,block 軸向溢出內容顯示在下一頁

符合 nonescroll 的媒體稱為 continuous media,符合 paged 的媒體稱為 paged media


3. 定位的核心:Containing Block(定位基準)

要理解元素「為何會出現在那個位置」,必須先理解:

所有定位元素(absolutefixedsticky),都需要一個 containing block 作為它的「座標系統」。
containing block 是元素用來計算「位置與大小」的參考框架。

這個基準 不一定是肉眼看到的父層,而是由 CSS 規範定義的演算法計算而來。

在 CSS Positioned Layout Level 3 中,明確定義了 positioned box 的 containing block 如何由祖先盒子建立。 (可參考 MDN:position

也就是

  • static:沒有 inset(top/right/bottom/left)與 z 軸偏移
  • relative / sticky:在文流中排版,再相對原本位置偏移
  • absolute / fixed:脫離文流,以特定祖先或 viewport 為座標系統

下面簡單比較一下各個position的模式

position是否脫離文流偏移是否生效基準位置(Containing Block)
static正常排版,不能偏移
relative自己原本的位置(original position)
absolute最近建立 absolute containing block 的祖先,若沒有則為 initial containing block
fixed一般為 layout viewport;也可能被最近的 fixed containing block 取代
sticky否(行為接近 relative,只在特定區間內「黏住」)自己原本位置 + 最近可捲動容器(scrollport)(參考 MDN:position

下面是重新整理後的第 4 章,章節編號完全保留,例子也稍微統一了一下風格,並補了一個 fixed「看起來失效」的例子。


4. 五種 position 的範例

4.1 static (預設)

  • 預設定位方式。
  • 不接受 top / right / bottom / left 等偏移屬性。
  • 完全跟著正常文流排版。
<div class="box box-a">A(static)</div>
<div class="box box-b">B(static)</div>
.box {
  padding: 8px;
  background: lightgray;
  margin-bottom: 8px;
}
 
/* 即使寫了 top/left,在 static 上也不會生效 */
.box-a {
  position: static;
  top: 40px; /* 無效 */
  left: 40px; /* 無效 */
}

4.2 relative(保留版面,只偏移視覺)

  • 元素仍占據原本的版面空間。
  • 使用 top / left / right / bottom 只會改變「繪製位置」,不影響其他元素的排版。
<div class="box box-rel">relative:占原本空間,視覺上向右下偏移</div>
.box-rel {
  position: relative;
  top: 10px; /* 視覺向下 10px,原本空間仍保留 */
  left: 20px; /* 視覺向右 20px */
  background: lightblue;
}

4.3 absolute(脫離文流,依 containing block 定位)

  • 元素脫離正常文流,不再占據原本文流空間。
  • 位置由「包含區塊(containing block)」決定。
  • 常見做法:在父層加 position: relative,讓子層以父層為基準。
<div class="parent">
  容器(parent)
  <div class="child">child(absolute)</div>
</div>
.parent {
  position: relative; /* 建立 containing block */
  width: 200px;
  height: 120px;
  padding: 8px;
  background: #eee;
}
 
.child {
  position: absolute;
  top: 0; /* 以 .parent 的 padding box 左上角為 (0,0) */
  right: 0;
  padding: 4px 8px;
  background: salmon;
}

4.4 fixed(相對 viewport 固定)

  • 預期行為:相對於視窗(viewport)固定,不隨頁面捲動而移動。
  • 常用在浮動按鈕、回到頂部按鈕、固定工具列等。
<!-- 捲動畫面時,按鈕會固定在視窗右下角 -->
 
<body>
  <div>
    <button class="fab">+</button>
    <div class="spacer">
      很多內容很多內容很多內容很多內容很多內容很多內容很多內容很多內容
    </div>
  </div>
</body>
body {
  margin: 0;
  height: 2000px; /* 方便測試捲動 */
}
 
.fab {
  position: fixed;
  right: 16px;
  bottom: 16px;
  padding: 12px 16px;
  border-radius: 999px;
  background: black;
  color: white;
}

fixed 看起來「失效」的例子

有些情況下,fixed 會不再以 viewport 為基準,而是跟著某個祖先元素走,導致看起來像「失效」。

以下是常見狀況之一:祖先有 transform,導致該祖先成為 fixed containing block。

<body>
  <div class="wrapper">
    <button class="fab-inside">+</button>
    <div class="spacer">
      很多內容很多內容很多內容很多內容很多內容很多內容很多內容很多內容
    </div>
  </div>
</body>
body {
  margin: 0;
  height: 2000px; /* 方便測試捲動 */
}
 
/* 這個 transform 會讓 .wrapper 成為 fixed 的 containing block */
.wrapper {
  transform: translateZ(0);
  padding: 16px;
  background: #f5f5f5;
}
 
.spacer {
  height: 1500px;
}
 
.fab-inside {
  position: fixed;
  right: 16px;
  bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: darkslategray;
  color: white;
}

在這個例子裡:

  • 頁面整體捲動時,.wrapper 會跟著一起移動。
  • .fab-inside 雖然是 position: fixed,但因為 .wrappertransform,它是相對 .wrapper 的「視窗」固定。
  • 結果就是:按鈕會跟著 .wrapper 一起移動,而不是固定在整個瀏覽器視窗。

除了 transform 外,以下屬性也會建立 fixed containing block,造成類似問題:

  • contain: layout / paint / strict
  • will-change: transform / scroll-position

4.5 sticky(在區域內黏住)

  • 介於 relative 和「區域內固定」之間的行為。
  • 元素一開始像 relative 一樣正常排版。
  • 捲動到某個位置後,會在指定位置「黏住」。
<header class="header">Sticky Header</header>
<main class="content">
  <!-- 很多內容... -->
  <p>內容內容內容內容內容內容內容內容內容內容內容內容內容內容內容內容</p>
  <p>內容內容內容內容內容內容內容內容內容內容內容內容內容內容內容內容</p>
  <p>內容內容內容內容內容內容內容內容內容內容內容內容內容內容內容內容</p>
  <!-- ... -->
</main>
.header {
  position: sticky;
  top: 0; /* 捲到頂部時開始黏住 */
  padding: 8px 16px;
  background: white;
  border-bottom: 1px solid #ddd;
}
 
.content {
  height: 2000px; /* 製造足夠的捲動距離 */
}

sticky 生效時要注意:

  • 一定要有 top/bottom 等 inset 屬性。
  • 需要有可以捲動的內容。
  • 如果中間某個祖先建立了新的捲動容器,並且有 overflow: auto / scroll / hidden,元素的黏住範圍就會被限制在那個容器內。

5. 影響定位基準與疊放的父層屬性

5.1 影響定位基準(Containing Block)

依 CSS Position-3 規範,下列會成為定位基準:

  • positionstatic
  • transformnone
  • contain: layout | paint | strict
  • will-change 指向相關屬性

影響:absolutefixed 座標都會改以最近的這個祖先為基準。

5.2 影響疊放層級(Stacking Context)

會建立 stacking context 的條件:

  • z-index 作用於非 static 的元素
  • position: fixed/sticky
  • opacity < 1
  • transform / filter
  • clip-path / mask
  • contain(layout/paint/content)
  • isolation: isolate
  • top layer 元素

重點:**子層的 z-index 無法超過父層所在的 stacking context。

5.3 iframe 特性

  • iframe 擁有自己的 viewport。
  • 它內部的 fixed 永遠只會固定在 iframe 視窗內。

5.4 sticky 的必要條件

  • 有 inset(如 top:0
  • 祖先不能裁切內容(overflow 不可為 hidden/auto/scroll)
  • 父層高度需足夠

6. 常見實務與陷阱整理

6.1 定位總結

  1. absolute 跟父層走 → 父層加 position: relative
  2. fixed 異常 → 檢查 transform / contain / will-change
  3. 全頁 overlay 放在 body 直屬子層
  4. sticky 無效 → 檢查 inset、overflow、父層高度
  5. 大型布局 → 用 Flex / Grid;position 用於局部 UI(tooltip、modal、badge)

6.2 定位基準表

行為Containing Block
relative元素原始位置
absolute最近建立 containing block 的祖先
fixed(正常)viewport
fixed(被干擾)最近的 transform/contain 祖先
sticky原始位置 + 最近 scroll container

6.3 z-index 常見陷阱

陷阱 1:沒有 positioned → z-index 無效

.badge {
  z-index: 999;
} /* 無效:position 是 static */
.badge {
  position: relative;
} /* 才有效 */

陷阱 2:子層無法超越父層 stacking context

父層建立 stacking context → 子層再大 z-index 也跳不出來。

陷阱 3:opacity / transform 意外建立 stacking context

動畫常用屬性(opacity、transform、filter)
→ 會讓內部 z-index 隔離,不可跨越。

7. 結語:定位的本質是「座標系統」與「層級系統」

深入理解 CSS 定位,不僅是記憶屬性,而是要能夠釐清並回答兩個關鍵問題:

  1. 座標系統:

    • 該元素的 top/left 是相對於哪一個 containing block 進行計算的?
  2. 層級系統:

    • 它所處的 stacking context 位於哪一層級?
    • 在該上下文內,z-index 的排序為何?

可以用一句話總結整篇文章的核心概念:

Containing block 定義了座標系統,子元素的位置基準跟隨它走; Stacking context 建立了層級系統,元素重疊時的 z-index 排序由它掌控。