徹底搞懂CSS層疊上下文、層疊等級、層疊順序、z-index

  • 2019 年 11 月 22 日
  • 筆記

原文作者:MagicEyes

原文鏈接:https://juejin.im/post/5b876f86518825431079ddd6#comment

前言

最近,在項目中遇到一個關於 CSS 中元素z-index屬性的問題,具體問題不太好描述,總結起來就是當給元素和父元素色設置position屬性和z-index相關屬性後,頁面上渲染的元素層級結果和我預想的不一樣。根據自己之前的理解,也沒找到一個合理的解釋。我知道,肯定是我對相關屬性的細節理解存在問題,所以結合官方文檔和在網上各種搜集整理,明白了其中的原因。寫下這篇文章,和大家分享有關 CSS 中層疊上下文層疊等級層疊順序以及z-index相關的一整套技術細節。

如果存在什麼錯誤或重要遺漏或者有什麼疑問,歡迎留言指正、討論!感謝!

本文已同步至我的個人主頁。更多內容,歡迎訪問我的 GitHub 主頁[1],謝謝關注和支援!

一個「片面」的理解

以往,由於自己使用z-index的頻率不大,所以對這個 CSS 屬性存在比較片面的認識。一直認為z-index就是用來描述定義一個元素在螢幕Z軸上的堆疊順序。z-index值越大在Z軸上就越靠上,也就是離螢幕觀察者越近。最後才發現這個認識存在很大的問題:

  1. 首先,z-index屬性值並不是在任何元素上都有效果。它僅在定位元素(定義了position屬性,且屬性值為非static值的元素)上有效果。
  2. 判斷元素在Z軸上的堆疊順序,不僅僅是直接比較兩個元素的z-index值的大小,這個堆疊順序實際由元素的層疊上下文、層疊等級共同決定。

要想完全理解一個東西,首先要明白它是什麼,也就是它的定義。我們先看看上面提到的層疊上下文層疊等級層疊順序都是什麼?定義又太過抽象,後面會再用一個具象的比喻來讓你徹底明白它們到底是什麼,有什麼聯繫。

什麼是「層疊上下文」

層疊上下文(stacking context),是 HTML 中一個三維的概念。在 CSS2.1 規範中,每個盒模型的位置是三維的,分別是平面畫布上的X軸Y軸以及表示層疊的Z軸。一般情況下,元素在頁面上沿X軸Y軸平鋪,我們察覺不到它們在Z軸上的層疊關係。而一旦元素髮生堆疊,這時就能發現某個元素可能覆蓋了另一個元素或者被另一個元素覆蓋。

如果一個元素含有層疊上下文,(也就是說它是層疊上下文元素),我們可以理解為這個元素在Z軸上就「高人一等」,最終表現就是它離螢幕觀察者更近。

具象的比喻:你可以把層疊上下文元素理解為理解為該元素當了官,而其他非層疊上下文元素則可以理解為普通群眾。凡是「當了官的元素」就比普通元素等級要高,也就是說元素在Z軸上更靠上,更靠近觀察者。

什麼是「層疊等級」

那麼,層疊等級指的又是什麼?層疊等級(stacking level,叫「層疊級別」/「層疊水平」也行)

  • 在同一個層疊上下文中,它描述定義的是該層疊上下文中的層疊上下文元素在Z軸上的上下順序。
  • 在其他普通元素中,它描述定義的是這些普通元素在Z軸上的上下順序。

說到這,可能很多人疑問了,不論在層疊上下文中還是在普通元素中,層疊等級都表示元素在Z軸上的上下順序,那就直接說它描述定義了所有元素在Z軸上的上下順序就 OK 啊!為什麼要分開描述?

為了說明原因,先舉個栗子:

具象的比喻:我們之前說到,處於層疊上下文中的元素,就像是元素當了官,等級自然比普通元素高。再想像一下,假設一個官員 A 是個省級領導,他下屬有一個秘書 a-1,家裡有一個保姆 a-2。另一個官員 B 是一個縣級領導,他下屬有一個秘書 b-1,家裡有一個保姆 b-2。a-1 和 b-1 雖然都是秘書,但是你想一個省級領導的秘書和一個縣級領導的秘書之間有可比性么?甚至保姆 a-2 都要比秘書 b-1 的等級高得多。誰大誰小,誰高誰低一目了然,所以根本沒有比較的意義。只有在 A 下屬的 a-1、a-2 以及 B 下屬的 b-1、b-2 中相互比較大小高低才有意義。

再類比回「層疊上下文」和「層疊等級」,就得出一個結論:

  1. 普通元素的層疊等級優先由其所在的層疊上下文決定。
  2. 層疊等級的比較只有在當前層疊上下文元素中才有意義。不同層疊上下文中比較層疊等級是沒有意義的。

如何產生「層疊上下文」

前面說了那麼多,知道了「層疊上下文」和「層疊等級」,其中還有一個最關鍵的問題:到底如何產生層疊上下文呢?如何讓一個元素變成層疊上下文元素呢?

其實,層疊上下文也基本上是有一些特定的 CSS 屬性創建的,一般有 3 種方法:

  1. HTML中的根元素<html></html>本身 j 就具有層疊上下文,稱為「根層疊上下文」。
  2. 普通元素設置position屬性為非static值並設置z-index屬性為具體數值,產生層疊上下文。
  3. CSS3 中的新屬性也可以產生層疊上下文。

至此,終於可以上程式碼了,我們用程式碼說話,來驗證上面的結論:

栗子 1: 有兩個 div,p.a、p.b 被包裹在一個 div 里,p.c 被包裹在另一個盒子里,只為.a、.b、.c 設置positionz-index屬性

<style>    div {      position: relative;      width: 100px;      height: 100px;    }    p {      position: absolute;      font-size: 20px;      width: 100px;      height: 100px;    }    .a {      background-color: blue;      z-index: 1;    }    .b {      background-color: green;      z-index: 2;      top: 20px;      left: 20px;    }    .c {      background-color: red;      z-index: 3;      top: -20px;      left: 40px;    }  </style>    <body>    <div>      <p class="a">a</p>      <p class="b">b</p>    </div>      <div>      <p class="c">c</p>    </div>  </body> 

效果:

因為 p.a、p.b、p.c 三個的父元素 div 都沒有設置z-index,所以不會產生層疊上下文,所以.a、.b、.c 都處於由<html></html>標籤產生的「根層疊上下文」中,屬於同一個層疊上下文,此時誰的z-index值大,誰在上面。

栗子 2:有兩個 div,p.a、p.b 被包裹在一個 div 里,p.c 被包裹在另一個盒子里,同時為兩個 div 和.a、.b、.c 設置positionz-index屬性

<style>    div {      width: 100px;      height: 100px;      position: relative;    }    .box1 {      z-index: 2;    }    .box2 {      z-index: 1;    }    p {      position: absolute;      font-size: 20px;      width: 100px;      height: 100px;    }    .a {      background-color: blue;      z-index: 100;    }    .b {      background-color: green;      top: 20px;      left: 20px;      z-index: 200;    }    .c {      background-color: red;      top: -20px;      left: 40px;      z-index: 9999;    }  </style>    <body>    <div class="box1">      <p class="a">a</p>      <p class="b">b</p>    </div>      <div class="box2">      <p class="c">c</p>    </div>  </body>    作者:MagicEyes  鏈接:https://juejin.im/post/5b876f86518825431079ddd6  來源:掘金  著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

效果:

我們發下,雖然p.c元素的z-index值為 9999,遠大於p.ap.bz-index值,但是由於p.ap.b的父元素div.box1產生的層疊上下文的z-index的值為 2,p.c的父元素div.box2所產生的層疊上下文的z-index值為 1,所以p.c永遠在p.ap.b下面。

同時,如果我們只更改p.ap.bz-index值,由於這兩個元素都在父元素div.box1產生的層疊上下文中,所以,誰的z-index值大,誰在上面。

什麼是「層疊順序」

說完「層疊上下文」和「層疊等級」,我們再來說說「層疊順序」。「層疊順序」(stacking order)表示元素髮生層疊時按照特定的順序規則在Z軸上垂直顯示。由此可見,前面所說的「層疊上下文」和「層疊等級」是一種概念,而這裡的「層疊順序」是一種規則。

在不考慮 CSS3 的情況下,當元素髮生層疊時,層疊順訊遵循上面途中的規則。這裡值得注意的是:

  1. 左上角"層疊上下文background/border"指的是層疊上下文元素的背景和邊框。
  2. inline/inline-block元素的層疊順序要高於block(塊級)/float(浮動)元素。
  3. 單純考慮層疊順序,z-index: autoz-index: 0在同一層級,但這兩個屬性值本身是有根本區別的。

對於上面第 2 條,為什麼inline/inline-block元素的層疊順序要高於block(塊級)/float(浮動)元素?這個大家可以思考一下!其實很簡單,像border/background屬於裝飾元素的屬性,浮動和塊級元素一般用來頁面布局,而網頁設計之初最重要的就是文字內容,所以在發生層疊時會優先顯示文字內容,保證其不被覆蓋。

你要的「套路」

上面說了那麼多,可能你還是有點懵。這麼多概念規則,來點最實際的,有沒有一個「套路」當遇到元素層疊時,能很清晰地判斷出他們誰在上誰在下呢?答案是——肯定有啊!

1、首先先看要比較的兩個元素是否處於同一個層疊上下文中:1.1 如果是,誰的層疊等級大,誰在上面(怎麼判斷層疊等級大小呢?——看「層疊順序」圖)。1.2 如果兩個元素不在統一層疊上下文中,請先比較他們所處的層疊上下文的層疊等級。2、當兩個元素層疊等級相同、層疊順序相同時,在 DOM 結構中後面的元素層疊等級在前面元素之上。

光說不練假把式

對於技術學習,程式碼展示是最直觀最易懂的方式之一。話不多說,直接上程式碼,我們通過以下幾個「栗子」,來進一步驗證掌握上面的結論。

栗子 3:

<style>    .box1, .box2 {      position: relative;      z-index: auto;    }    .child1 {      width: 200px;      height: 100px;      background: #168bf5;      position: absolute;      top: 0;      left: 0;      z-index: 2;    }    .child2 {      width: 100px;      height: 200px;      background: #32c292;      position: absolute;      top: 0;      left: 0;      z-index: 1;    }  </style>  </head>    <body>    <div class="box1">      <div class="child1"></div>    </div>      <div class="box2">      <div class="child2"></div>    </div>  </body>

效果:

說明:.box1/.box2雖然設置了position: relative,但是z-index: auto的情況下,這兩個div還是普通元素,並沒有產生層疊上下文。所以,child1/.child2屬於<html></html>元素的「根層疊上下文」中,此時,誰的z-index值大,誰在上面。

栗子 4:

對於栗子 1 中的 CSS 程式碼,我們只把.box1/.box2z-index屬性值改為數值0,其餘不變。

.box1, .box2 {    position: relative;    z-index: 0;  }  ...

效果:

說明:此時,我們發現,僅僅修改了.box1/.box2z-index屬性值改為數值0,最終結果完全相反。這時.child2覆蓋在了.child1上面。原因是什麼呢?很簡單:因為設置z-index: 0後,.box1/.box2產生了各自的層疊上下文,這時候要比較.child1/.child2的層疊關係完全由父元素.box1/.box2的層疊關係決定。但是.box1/.box2z-index值都為0,都是塊級元素(所以它們的層疊等級,層疊順序是相同的),這種情況下,在DOM結構中後面的覆蓋前面的,所以.child2就在上面。

CSS3 中的屬性對層疊上下文的影響

CSS3 中出現了很多新屬性,其中一些屬性對層疊上下文也產生了很大的影響。如下:

  1. 父元素的 display 屬性值為flex|inline-flex,子元素z-index屬性值不為auto的時候,子元素為層疊上下文元素;
  2. 元素的opacity屬性值不是1
  3. 元素的transform屬性值不是none
  4. 元素mix-blend-mode屬性值不是normal`;
  5. 元素的filter屬性值不是none
  6. 元素的isolation屬性值是isolate
  7. will-change指定的屬性值為上面任意一個;
  8. 元素的-webkit-overflow-scrolling屬性值設置為touch

CSS3 中,元素屬性滿足以上條件之一,就會產生層疊上下文。我們用第 1 條來做一個簡單的解釋說明。

栗子 5:

<style>    .box {    }    .parent {      width: 200px;      height: 100px;      background: #168bf5;      /* 雖然設置了z-index,但是沒有設置position,z-index無效,.parent還是普通元素,沒有產生層疊上下文 */      z-index: 1;    }    .child {      width: 100px;      height: 200px;      background: #32d19c;      position: relative;      z-index: -1;    }  </style>  </head>    <body>    <div class="box">      <div class="parent">        parent        <div class="child">child</div>      </div>    </div>  </body>

效果:

說明:我們發現,.child.parent覆蓋了。按照「套路」來分析一下:雖然.parent設置了z-index屬性值,但是沒有設置position屬性,z-index無效,所以沒有產生層疊上下文,.parent還是普通的塊級元素。此時,在層疊順序規則中,z-index值小於0.child會被普通的block塊級元素.parent覆蓋。

栗子 6:

對於上面的栗子,我們只修改.box 的屬性,設置 display: flex,其餘屬性和 DOM 結構不變。

.box {    display: flex;  }

效果:

說明:當給.box設置display: flex時,.parent就變成層疊上下文元素,根據層疊順序規則,層疊上下文元素的background/border的層疊等級小於z-index值小於0的元素的層疊等級,所以z-index值為-1.child.parent上面。

小測試

下面的程式碼,我會把最終頁面渲染的結果放在程式碼之後,有興趣的「童鞋」可以分析一下,各個元素的層疊等級,最後來確定這些元素哪個在上哪個在下。

<style>    .parent {      width: 100px;      height: 200px;      background: #168bf5;      position: absolute;      top: 0;      left: 0;      z-index: 0;    }    .child1 {      width: 100px;      height: 200px;      background: #32d19c;      position: absolute;      top: 20px;      left: 20px;      z-index: 1;    }    .child2 {      width: 100px;      height: 200px;      background: #e4c950;      position: absolute;      top: 40px;      left: 40px;      z-index: -1;    }    .child2-1 {      width: 100px;      height: 200px;      background: #e45050;      position: absolute;      top: 60px;      left: 60px;      z-index: 9999;    }    .child2-2 {      width: 100px;      height: 200px;      background: #db68a7;      position: absolute;      top: 80px;      left: 40px;      z-index: -9999;    }  </style>  </head>    <body>    <div class="parent">      parent      <div class="child1">child1</div>      <div class="child2">        child2        <div class="child2-1">child2-1</div>        <div class="child2-2">child2-2</div>      </div>    </div>  </body>

效果:

參考文章

以上的內容有一部分內容參考了以下兩位大神的部落格,寫的很好,也很清晰,推薦大家看一看,你會對相關知識點掌握的更清晰。

  1. 張鑫旭-《深入理解 CSS 中的層疊上下文和層疊順序》[2]
  2. AMInInsist-《CSS 中的 z-index 屬性》[3]

推薦文章

下面的文章鏈接是我在學習實踐過程中看到的一些覺得比較好的文章,有興趣霍有需要的朋友可以參考參考,希望可以幫你徹底弄清文章中涉及的這些問題。

  1. lijinxieyang-《層疊上下文【stacking context】與層疊順序【stacking order】》[4]
  2. w3help.org – 《KB013: 分層的顯示( Layered presentation )》[5]
  3. MDN-《The stacking context》[6]
  4. 尚-《css 之層疊上下文和層疊順序》[7]

參考資料

[1]我的GitHub主頁: https://webcaolixin.github.io/

[2]張鑫旭-《深入理解CSS中的層疊上下文和層疊順序》: https://www.zhangxinxu.com/wordpress/2016/01/understand-css-stacking-context-order-z-index/

[3]AMInInsist-《CSS 中的z-index屬性》: https://blog.csdn.net/weixin_40672882/article/details/81183321

[4]lijinxieyang-《層疊上下文【stacking context】與層疊順序【stacking order】》: http://www.w3help.org/zh-cn/kb/013/

[5]w3help.org – 《KB013: 分層的顯示( Layered presentation )》: http://www.w3help.org/zh-cn/kb/013/

[6]MDN-《The stacking context》: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context

[7]尚-《css之層疊上下文和層疊順序》: https://www.cnblogs.com/chenshanyuanzi/p/7700823.html