前端基礎

-

深入解析 CSS z-index 和 stacking context 堆疊上下文

this.web

你有發生過設定 z-index: 999 的元素蓋不過 z-index: 0 的元素嗎,今天我通過這篇文,帶你清楚了解 z-index 以及瀏覽器的元素先後比較機制。

z-index 是什麼

z-index 在 CSS 中很重要的觀念,z-index 是用來控制元素在 Z 軸上的堆疊順序(即前後順序)的屬性。當多個元素重疊時,z-index 會決定哪些元素會顯示在前面,哪些會被擋在後面。

可以想像他是垂直穿過電腦螢幕,射向你的一條線,也就是 z-index 越高的元素就越靠近你,他就會蓋在其他元素上面。

怎麼使用 z-index

z-index 只能用在定位元素上,也就是設定 position: relative、absolute、fiexd、sticky 的元素上面。

/* 非 position: static 的元素才能設定 z-index */
div {
  position: relative;
  position: absolute;
  position: fixed;
  position: sticky;
}

瀏覽器的預設圖層順序

在沒有設置 z-index 的情況下,瀏覽器會按照元素在 HTML 中的出現順序進行堆疊,後寫的元素會在先寫的元素上面:

<div class="div1"></div>
<div class="div2"></div>
div {
  width: 150px;
  height: 150px;
  border-radius: 16px;
}
.div1 {
  background: var(--blue);
}
.div2 {
  transform: translate(25%, -25%);
  background: var(--red);
}

在這個例子中,由於 .div2 後寫,所以它會顯示在 .div1 之上。

當設定 z-index 後,就會變得較複雜,讓我們接著看下去。

一、兄弟元素的 z-index 比較:

當兩個元素為兄弟元素時,堆疊順序的比較可以分為以下幾種情況:

  • 相同條件
  • 不同條件

A、相同條件

  • 都不是定位元素
  • 都是定位元素但都沒有設定 z-index
  • 都是定位元素且有相同的 z-index

這些情況下,後寫的元素會蓋在先寫的元素上面。

<div class="div1"></div>
<div class="div2"></div>
div {
  position: relative;
  width: 150px;
  height: 150px;
  border-radius: 16px;
}
.div1 {
  background: var(--blue);
}
.div2 {
  transform: translate(25%, -25%);
  background: var(--red);
}

上面都是定位元素,但因為都沒有設定 z-index,所以會依照瀏覽器預設的排列。

相同條件

B、不同條件

  • 當兄弟元素都有設定 z-indexz-index 較高的元素會蓋在上面。
div {
  position: relative;
  width: 150px;
  height: 150px;
  border-radius: 16px;
}
.div1 {
  background: var(--blue);
  z-index: 2;
}
.div2 {
  transform: translate(25%, -25%);
  background: var(--red);
  z-index: 1;
}

此時 div1 因為 z-index 較高的原因,會在 div2 上面。

而當一個元素有設定 z-index,另一個沒有時,會發生甚麼事情呢?可以分為三種情況來看

  • 設定 z-index = 0,則依照瀏覽器的預設情況比較,後寫的在先寫的上面。
  • 設定 z-index > 0,則會蓋住沒有設 z-index 的元素
  • 設定 z-index < 0,則會被沒有設 z-index 的元素蓋住。

設定 z-index = 0

此時 div1 會在 div2 下面,因為 div2 是後寫的。

.div1 {
  background: var(--blue);
  position: absolute;
  z-index: 0;
}
.div2 {
  background: var(--red);
  position: absolute;
   /* 沒有設定 z-index */
}
設定 z-index = 0

設定 z-index > 0

此時 div1 會在 div2 上面,因為 div1 的 z-index > 0。

.div1 {
  background: var(--blue);
  position: absolute;
  z-index: 1;
}
.div2 {
  background: var(--red);
  position: absolute;
   /* 沒有設定 z-index */
}
設定 z-index > 0

設定 z-index < 0

此時 div2 會在 div1 上面,因為 div1 的 z-index < 0。

.div1 {
  background: var(--blue);
  position: absolute;
  z-index: -1;
}
.div2 {
  background: var(--red);
  position: absolute;
   /* 沒有設定 z-index */
}
設定 z-index < 0

二、子元素與父元素的 z-index 比較:

絕大多數情況下,子元素的層級都大於父元素。但有一個例外:

  • 如果子元素有 position 屬性且 z-index < 0,它會被沒有設定 z-index 的父元素蓋住
<div class="parent">
  <div class="child"></div>
</div>
/* 有 position 屬性但 z-index < 0 的會被沒有 position 的蓋住 */
/* 這個例子中,因為 div2 的 z-index 為負,會被沒有設定 z-index 的父元素蓋住 */
.parent {
  position: unset;
  background: var(--blue);
}

.child {
  position: relative;
  z-index: -1;
  background: var(--red);
  transform: translate(25%, -25%);
}
如果子元素有 position 屬性且 z-index < 0,它會被沒有設定 z-index 的父元素蓋住。

要注意的是,只要父元素一設定 z-index,不管大小,都會被子元素蓋過。

.parent {
  position: relative;
  z-index: 100; /* 儘管父元素的 z-index 比子元素大,還是會被子元素蓋過 */
  background: var(--blue);
}

.child {
  position: relative;
  z-index: -1;
  transform: translate(75%, 75%);
  background: var(--red);
}
只要父元素一設定 z-index,不管大小,都會被子元素蓋過。

三、子元素和其它親戚元素的 z-index 比較:

親戚元素是指和父母的兄弟姊妹(阿姨叔叔)或表哥表弟元素比較。如下面的 code,you unclecousin 比較。

<div class="uncle">
  <div class="cousin"></div>
</div>

<div class="father">
  <div class="you"></div>
</div>

當和叔叔元素、表弟元素比較時,都必須考慮父元素的 position 屬性和 z-index 值:

fatheruncle 都為定位元素(有 position 屬性),且 z-index:father > uncle,則 cousin 永遠不可能比 you 層級高。

/* father > uncle 所以 you 永遠 > cousin */
.uncle {
  position: relative;
  z-index: 1;
  background: var(--blue);
}

.cousin {
  position: relative;
  z-index: 100;
  transform: translate(25%, 25%);
  background: var(--red);
}

.father {
  position: relative;
  z-index: 2;
}

.you {
  position: relative;
  z-index: -1;
  transform: translate(50%, -25%);
  background: var(--yellow);
}
若 father 和 uncle  都為定位元素(有 position 屬性),且 z-index:father > uncle,則 cousin 永遠不可能比 you 層級高。

若父元素沒有設定 position則子元素會與父元素的兄弟元素(叔叔元素)進行比較,忽略父元素。

而由於 you 寫在 uncle 之後,且都沒有設定 z-index,這裡就會照瀏覽器預設的方式排列。you 會在 uncle 上面。

<div class="uncle"></div>

<div class="father">
  <div class="you"></div>
</div>
/* 若父元素 div2 沒有設定 position,則子元素 div3 會忽略父元素跟其他父元素的兄弟元素 div1 比較 */
/* div3 寫在 div1 後,所以 div3 > div1 */
.uncle {
  position: relative;
  background: var(--blue);
}

.father {
  position: unset;
}
.you {
  position: relative;
  transform: translate(75%, -25%);
  background: var(--green);
}
若父元素沒有設定 position,則子元素會與父元素的兄弟元素(叔叔元素)進行比較

為什麼 z-index 這麼複雜?

其實,z-index 的複雜性來自於堆疊上下文(Stacking Context)。理解這一點後,z-index 的行為就不再那麼神秘了。

堆疊上下文(Stacking Context)

當一個定位元素有被設定 z-index 時,他就會創建堆疊上下文。而堆疊上下文具有三種特性:

  1. 獨立性:每個堆疊上下文內的元素和外部的元素是獨立的。這意味著內部元素的 z-index 和外部元素的 z-index 沒有關係。
  2. 上升性:每個堆疊上下文的最外層元素會上升到根堆疊上下文(root),並與其他堆疊上下文的元素進行比較。
  3. 內部排序性:每個堆疊上下文內部元素的堆疊順序如下(從低到高):
    1. 背景和邊框
    2. z-index 的元素
    3. z-indexauto0 的元素
    4. z-index 的元素

當知道這三個特性後,再回去看實例,應該就能理解 z-index 的比較了。

總結

z-index 的比較相對複雜,但大多數場景下只需記住一個簡單的規則:”子元素的堆疊順序通常不能超越父元素的堆疊上下文” 就能解決大部分的應用場景了~

相關系列文章