理解網頁元素的垂直排序規則以及善用 CSS STACKING CONTEXT

fin
9 min readApr 13, 2021

在排版頁面元素時,你是否發生過以下狀況:

不小心蓋到不該蓋的元素,或是被不該在上面的元素蓋掉
常常搞不定頁面上 z-index 順序,9999 結果還是被蓋過?
z-index: -1 結果東西被其他元素的背景蓋掉?
我想要 X 永遠都在 Y 與 Z 中間,但….

本文試著釐清元素垂直排序的規則,這裡先用一張圖來展示所謂的垂直排序,也可以到 Codepen 玩玩: https://codepen.io/finfin/pen/JjEMzBe

https://codepen.io/finfin/pen/JjEMzBe

以這張圖為例,垂直排列順序為(由下至上):

#1 < #4 < #6 < #5 < #3 < #2

然後 DOM 結構為

<DIV #1></DIV #1>
<DIV #2></DIV #2>
<DIV #3>
<DIV #4></DIV #4>
<DIV #5></DIV #5>
<DIV #6></DIV #6>
</DIV #3>

你可能會想說,為什麼大家都有 z-index 但看起來好像沒有按照順序排列?為什麼 DIV #4 的 z-index 6 在 DIV #1 的 z-index 5 下面?其實這些都是有規則的,就讓我們試著來拆解這些規則吧。

排序層級

瀏覽器的呈現有分三個層級:

1.背景 (BACKGROUND) 與 邊界 (BORDER)
2. 一般靜態位置 position: static 元素
3. 自訂位置元素 (position 不等於 static)
在這個層級內,我們還有兩種工具可以調整順序
3–1. 自訂位置元素可以用 z-index 來改變排序
3–2. 建立 stacking context (後稱堆疊)來讓其內的所有元素,都在彼此身旁

排序基本規則

再講順序之前,很重要不能不提的是,在同一個層級中,如果沒有透過其他方式改變排序,後出現的節點 (Node) 一定會蓋掉前面的。節點的前後順序指的就是該節點在 HTML 中出現的順序。

為什麼這邊講的是『節點』?主要是因為 CSS 主要處理的是『HTML 元素』的樣式,比如 div, h1, img 等等,我們講的 z-index, stacking context 等等,都是從 HTML 元素的樣式而來。但 HTML 元素之內的文字會形成一個『節點』,在討論排序時,有沒有這個節點會有很大的差異。如果只看元素的話,那麼一般父元素的文字內容應該要被子元素覆蓋,但實際情況是,如果父元素的文字內容是出現在子元素之後的話,那一樣會蓋掉子元素,表示有除了『元素』之外的單位在影響著排序,也就是這裡提到的『節點』了,可以到範例玩玩看 https://codepen.io/finfin/pen/NWdyaje

排序一:背景 (BACKGROUND) 與 邊界 (BORDER)

背景與邊界會在其他東西的下面,若要再講詳細點的話,背景是最下層,邊界會再疊上去,再來才是元素的內容如文字、子元素等等。見下圖的虛線邊界,可見背景是被蓋在邊界底下的,並且邊界在文字底下。https://codepen.io/finfin/pen/jOyZLmZ

如果後面接著有另一個元素,他們之間的關係就是這樣,可以看出背景是在最下面一層 https://codepen.io/finfin/pen/OJWQjEj

排序二:一般靜態位置 position: static 元素

再來就是一般靜態位置元素的階層了,排序二的意思是,『所有』的這個層級的內容,一定都在排序一之上,就跟種姓制度差不多意思。這裡一樣預設會依照後蓋前的規則來棑序。前一張圖可以明顯看到背景與邊界屬於排序一的層級,而文字或其他元素,則屬於排序二的層級,一定會蓋在背景與邊界上方。『第二個 container』字樣會蓋在『第一個 container』字樣之上,則是依照後蓋前這個規則。

排序三:自訂位置元素 (position 不等於 static)

排序三,自訂位置元素,這是花樣最多的一個層級,也是常常是破版的元兇。

首先,這層預設是在背景、靜態位置之上。且一樣依照後蓋前的規則,如下圖 DIV #5 雖然出場順序最後理應要在最上面,但 DIV #1–4 都是更高層級的,因此把 #5 蓋在最下面。

排序三之一:z-index

只有自訂位置元素可以使用 z-index,其作用很簡單,決定一個元素在堆疊中的順序。前面有講到一般來說排序三預設是在背景&靜態元素之上,但透過把 z-index 設定為負數,我們可以讓他跑到背景、靜態元素的下面。z-index 為一個整數,範圍 ±2147483647,遇到一樣 z-index 時,則比較元素出現順序,後者在上。

https://codepen.io/finfin/pen/ExZEyBL

如果頁面元素好好安排規劃,z-index 使用到 10 層以上的機率不高。比較常見的是第三方套件、廣告用 9999999, 2147483647 之類,試著要搶到最上層,例如:蓋板廣告、Modal、Tooltip 等。

排序三之二:Stacking Context

在垂直排序上,瀏覽器有另一層機制叫做 stacking context。如果有用過 photoshop 之類的設計工具,堆疊就像圖層內的『群組(Grouping)』功能,讓你把一群圖層包在一起,這樣在排序的時候他們就一定會被排在一起。把這個堆疊往上移動,裡面所有東西就會跟著移動,若是往下移動則裡面所有東西也會跟著移動。若是沒用過 photoshop 或是對圖層概念不熟悉的,這裡再次看看文章開頭的圖片,想像一下這些圖層之間的上下關係 。

https://codepen.io/finfin/pen/JjEMzBe

透過堆疊,我們可以輕鬆地讓一群元素,固定排列在圖層中的某個順序,更棒的是,在這個群組中的順序再怎麼調整,都不會影響到群組外的元素。要嘛就是在這個堆疊的上方,要嘛就是在下方,堆疊之外的東西不會突然插在堆疊中間的某個圖層。

堆疊是可以巢狀產生的,比如 <body> 是我們最基礎的堆疊,在這堆疊中,DIV #3 建立了一個自己的堆疊,包裹了旗下的 DIV #4~6。他們的上下關如下圖(這裡要倒過來想像,body 在最下面,div #1 在最上層)

建立堆疊 Stacking Context

那麼,什麼時候會建立堆疊呢?以下列出幾種比較常見的,更詳細的可以看 MDN

  • html 元素
  • position: absoluteposition: relative 加上 auto 之外的 z-index
  • position: fixedposition: sticky
  • flex container 下有 index 的子元素
  • isolation: isolate
  • transform / filter / clip-path
推薦使用 isolation: isolate ,因為這個屬性只有一個行為,就是產生堆疊
看到此屬性就可以馬上理解,這個元素是為了產生堆疊而存在的

只要元素符合以上條件,則這個元素就會建立一個堆疊。在堆疊所在的層級中,元素就是依照前述的排序一~排序三規則來排序。以範例來說,div #3 建立了一個堆疊,因此排序時就是以 #1 ~ #3 來排序,堆疊內的 #4~5 就不會被外面排序影響,而是在自己的小圈圈裡面自己排序。

當然,堆疊是可以巢狀的,今天如果 #6 我在建立一個堆疊,裡面放了一些元素,就可能變成這下圖這樣

知道了堆疊的運作邏輯之後,可以幫我們簡化很多排版的順序。比如說頁面上有 A / B 兩個不同區塊,彼此都有許多複雜的 z-index 排序,這時候就可以幫兩邊都產生堆疊,就可以避免彼此元素互相穿插。搭配近年元件化的概念,我們就把連圖層也一同元件化了。

應用案例一

一個元件,有兩個圖層,一個想要做成背景圖層因此設定 z-index: -1,但因為負數的關係他跑到被父元素的背景擋住了: https://codepen.io/finfin/pen/LYxdRjK ,你有辦法在不改變 z-index 的情況下,讓他跑出來嗎?結果應該要像下圖

應用案例解法

問題的原因在於 .background 圖層的 z-index: -1 會造成塗層跑到父元素底下,為了避免這情況發生,我們可以幫 #element 建立堆疊比如說加上 isolation: isolate,這樣堆疊內的東西就一定都會在父元素 .container 之上,同時 .background.content 圖層後的行為仍然被保存著

小結

只要理解排序規則,垂直排序就在你的掌控中:

1.背景與邊界
2. 靜態位置元素
3. 自訂位置元素
3–1. 用 z-index 來改變排序
3–2. 建立堆疊來讓其內的所有元素,都排序在彼此身旁

參考資料

--

--