1. 程式人生 > >深入理解CSS中的層疊上下文和層疊順序

深入理解CSS中的層疊上下文和層疊順序

零、世間的道理都是想通的

在這個世界上,凡事都有個先後順序,凡物都有個論資排輩。比方說食堂排隊打飯,對吧,講求先到先得,總不可能一擁而上。再比如說話語權,老婆的話永遠是對的,領導的話永遠是對的。

在CSS屆,也是如此。只是,一般情況下,大家歌舞昇平,看不出什麼差異,即所謂的眾生平等。但是,當發生衝突發生糾葛的時候,顯然,是不可能做到完全等同的,先後順序,身份差異就顯現出來了。例如,傑克和羅斯,只能一人浮在木板上,此時,出現了衝突,結果大家都知道的。那對於CSS世界中的元素而言,所謂的“衝突”指什麼呢,其中,很重要的一個層面就是“層疊顯示衝突”。

預設情況下,網頁內容是沒有偏移角的垂直視覺呈現,當內容發生層疊的時候,一定會有一個前後的層疊順序產生,有點類似於真實世界中論資排輩的感覺。

而要理解網頁中元素是如何“論資排輩”的,就需要深入理解CSS中的層疊上下文和層疊順序。

我們大家可能都熟悉CSS中的z-index屬性,需要跟大家講的是,z-index實際上只是CSS層疊上下文和層疊順序中的一葉小舟。

一、什麼是層疊上下文

層疊上下文,英文稱作”stacking context”. 是HTML中的一個三維的概念。如果一個元素含有層疊上下文,我們可以理解為這個元素在z軸上就“高人一等”。

這裡出現了一個名詞-z軸,指的是什麼呢?

表示的是使用者與螢幕的這條看不見的垂直線(參見下圖示意-紅線):
網頁中z軸示意

層疊上下文是一個概念,跟「塊狀格式化上下文(BFC)」類似。然而,概念這個東西是比較虛比較抽象的,要想輕鬆理解,我們需要將其具象化。

怎麼個具象化法呢?

你可以把「層疊上下文」理解為當官:網頁中有很多很多的元素,我們可以看成是真實世界的芸芸眾生。真實世界裡,我們大多數人是普通老百姓們,還有一部分人是做官的官員。OK,這裡的“官員”就可以理解為網頁中的層疊上下文元素。

換句話說,頁面中的元素有了層疊上下文,就好比我們普通老百姓當了官,一旦當了官,相比普通老百姓而言,離皇帝更近了,對不對,就等同於網頁中元素級別更高,離我們使用者更近了。

你懂的

二、什麼是層疊水平

再來說說層疊水平。“層疊水平”英文稱作”stacking level”,決定了同一個層疊上下文中元素在z軸上的顯示順序。level這個詞很容易讓我們聯想到我們真正世界中的三六九等、論資排輩。真實世界中,每個人都是獨立的個體,包括同卵雙胞胎,有差異就有區分。例如,雙胞胎雖然長得像Ctrl+C/Ctrl+V得到的,但實際上,出生時間還是有先後順序的,先出生的那個就大,大哥或大姐。網頁中的元素也是如此,頁面中的每個元素都是獨立的個體,他們一定是會有一個類似的排名排序的情況存在。而這個排名排序、論資排輩就是我們這裡所說的“層疊水平”。層疊上下文元素的層疊水平可以理解為官員的職級,1品2品,縣長省長之類;對於普通元素,這個嘛……你自己隨意理解。

於是,顯而易見,所有的元素都有層疊水平,包括層疊上下文元素,層疊上下文元素的層疊水平可以理解為官員的職級,1品2品,縣長省長之類。然後,對於普通元素的層疊水平,我們的探討僅僅侷限在當前層疊上下文元素中。為什麼呢?因為否則沒有意義。

這麼理解吧~ 上面提過元素具有層疊上下文好比當官,大家都知道的,這當官的家裡都有丫鬟啊保鏢啊管家啊什麼的。所謂打狗看主人,A官員家裡的管家和B官員家裡的管家做PK實際上是沒有意義的,因為他們牛不牛逼完全由他們的主子決定的。一人得道雞犬升天,你說這和珅家裡的管家和七俠鎮婁知縣縣令家裡的管家有可比性嗎?李總理的祕書是不是分分鐘滅了你村支部書記的祕書(如果有)。

翻譯成術語就是:普通元素的層疊水平優先由層疊上下文決定,因此,層疊水平的比較只有在當前層疊上下文元素中才有意義。

你懂的

需要注意的是,諸位千萬不要把層疊水平和CSS的z-index屬性混為一談。沒錯,某些情況下z-index確實可以影響層疊水平,但是,只限於定位元素以及flex盒子的孩子元素;而層疊水平所有的元素都存在。

三、什麼是層疊順序

再來說說層疊順序。“層疊順序”英文稱作”stacking order”. 表示元素髮生層疊時候有著特定的垂直顯示順序,注意,這裡跟上面兩個不一樣,上面的層疊上下文和層疊水平是概念,而這裡的層疊順序是規則

在CSS2.1的年代,在CSS3還沒有出現的時候(注意這裡的前提),層疊順序規則遵循下面這張圖:
層疊順序

有人可能有見過類似圖,那個圖是很多很多年前老外繪製的,英文內容。而是更關鍵的是國內估計沒有同行進行過驗證與實踐,實際上很多關鍵資訊缺失。上面是我自己手動重繪的中文版同時補充很多其他地方絕對沒有的重要知識資訊。如果想要無水印高清大圖,點選這裡購買(0.5元)。

缺失的關鍵資訊包括:

  1. 位於最低水平的border/background指的是層疊上下文元素的邊框和背景色。每一個層疊順序規則適用於一個完整的層疊上下文元素。
  2. 原圖沒有呈現inline-block的層疊順序,實際上,inline-block和inline水平元素是同等level級別。
  3. z-index:0實際上和z-index:auto單純從層疊水平上看,是可以看成是一樣的。注意這裡的措辭——“單純從層疊水平上看”,實際上,兩者在層疊上下文領域有著根本性的差異。

下面我要向大家發問了,大家有沒有想過,為什麼內聯元素的層疊順序要比浮動元素和塊狀元素都高?
疑問

為什麼呢?我明明感覺浮動元素和塊狀元素要更屌一點啊。

嘿嘿嘿,我就不賣關子了,直接看下圖的標註說明:
層疊順序元素的標註說明

諸如border/background一般為裝飾屬性,而浮動和塊狀元素一般用作佈局,而內聯元素都是內容。網頁中最重要的是什麼?當然是內容了哈,對不對!

因此,一定要讓內容的層疊順序相當高,當發生層疊是很好,重要的文字啊圖片內容可以優先暴露在螢幕上。例如,文字和浮動圖片重疊的時候:

浮動和文字重疊

上面說的這些層疊順序規則還是老時代的,如果把CSS3也牽扯進來,科科,事情就不一樣了。

四、務必牢記的層疊準則

下面這兩個是層疊領域的黃金準則。當元素髮生層疊的時候,其覆蓋關係遵循下面2個準則:

  1. 誰大誰上:當具有明顯的層疊水平標示的時候,如識別的z-indx值,在同一個層疊上下文領域,層疊水平值大的那一個覆蓋小的那一個。通俗講就是官大的壓死官小的。
  2. 後來居上:當元素的層疊水平一致、層疊順序相同的時候,在DOM流中處於後面的元素會覆蓋前面的元素。

在CSS和HTML領域,只要元素髮生了重疊,都離不開上面這兩個黃金準則。因為後面會有多個例項說明,這裡就到此為止。

五、層疊上下文的特性

層疊上下文元素有如下特性:

  • 層疊上下文的層疊水平要比普通元素高(原因後面會說明);
  • 層疊上下文可以阻斷元素的混合模式(見此文第二部分說明);
  • 層疊上下文可以巢狀,內部層疊上下文及其所有子元素均受制於外部的層疊上下文。
  • 每個層疊上下文和兄弟元素獨立,也就是當進行層疊變化或渲染的時候,只需要考慮後代元素。
  • 每個層疊上下文是自成體系的,當元素髮生層疊的時候,整個元素被認為是在父層疊上下文的層疊順序中。

翻譯成真實世界語言就是:

  • 當官的比老百姓更有機會面見聖上;
  • 領導下去考察,會被當地官員阻隔只看到繁榮看不到真實民情;
  • 一個家裡,爸爸可以當官,孩子也是可以同時當官的。但是,孩子這個官要受爸爸控制。
  • 自己當官,兄弟不佔光。有什麼福利或者變故只會影響自己的孩子們。
  • 每個當官的都有屬於自己的小團體,當家眷管家發生摩擦磕碰的時候(包括和其他官員的家眷管家),都是要優先看當官的也就是主子的臉色。

六、層疊上下文的建立

賣了這麼多文字,到底層疊上下文是個什麼鬼,倒是拿出來瞅瞅啊!

哈哈。如同塊狀格式化上下文,層疊上下文也基本上是有一些特定的CSS屬性建立的。我將其總結為3個流派,也就是做官的3種途徑:

  1. 皇親國戚派:頁面根元素天生具有層疊上下文,稱之為“根層疊上下文”。
  2. 科考入選派:z-index值為數值的定位元素的傳統層疊上下文。
  3. 其他當官途徑:其他CSS3屬性。

//zxx: 下面很多例子是實時CSS效果,建議您去原地址瀏覽,以便預覽更準確的效果。

①. 根層疊上下文
指的是頁面根元素,也就是滾動條的預設的始作俑者<html>元素。這就是為什麼,絕對定位元素在left/top等值定位的時候,如果沒有其他定位元素限制,會相對瀏覽器視窗定位的原因。

②. 定位元素與傳統層疊上下文
對於包含有position:relative/position:absolute的定位元素,以及FireFox/IE瀏覽器(不包括Chrome等webkit核心瀏覽器)(目前,也就是2016年初是這樣)下含有position:fixed宣告的定位元素,當其z-index值不是auto的時候,會建立層疊上下文。

知道了這一點,有些現象就好理解了。

如下HTML程式碼:

<div style="position:relative; z-index:auto;">
    <img src="mm1.jpg" style="position:absolute; z-index:2;">    <-- 橫妹子 -->
</div>
<div style="position:relative; z-index:auto;">
    <img src="mm2.jpg" style="position:relative; z-index:1;">    <-- 豎妹子 -->
</div>

大家會發現,豎著的妹子(mm2)被橫著的妹子(mm1)給覆蓋了。

下面,我們對父級簡單調整下,把z-index:auto改成層疊水平一致的z-index:0, 程式碼如下:

<div style="position:relative; z-index:0;">
    <img src="mm1.jpg" style="position:absolute; z-index:2;">    <-- 橫妹子 -->
</div>
<div style="position:relative; z-index:0;">
    <img src="mm2.jpg" style="position:relative; z-index:1;">    <-- 豎妹子 -->
</div>

大家會發現,尼瑪反過來了,豎著的妹子(mm2)這回趴在了橫著的妹子(mm1)身上。

百合大法好

為什麼小小的改變會有想法的結果呢?
思考

差別就在於,z-index:0所在的<div>元素是層疊上下文元素,而z-index:auto所在的<div>元素是一個普通的元素,於是,裡面的兩個<img>妹子的層疊比較就不受父級的影響,兩者直接套用層疊黃金準則,這裡,兩者有著明顯不一的z-index值,因此,遵循“誰大誰上”的準則,於是,z-index2的那個橫妹子,就趴在了z-index1的豎妹子身上。

z-index一旦變成數值,哪怕是0,都會建立一個層疊上下文。此時,層疊規則就發生了變化。層疊上下文的特性裡面最後一條——自成體系。兩個<img>妹子的層疊順序比較變成了優先比較其父級層疊上下文元素的層疊順序。這裡,由於兩者都是z-index:0,層疊順序這一塊兩者一樣大,此時,遵循層疊黃金準則的另外一個準則“後來居上”,根據在DOM流中的位置決定誰在上面,於是,位於後面的豎著的妹子就自然而然趴在了橫著的妹子身上。對,沒錯,<img>元素上的z-index打醬油了!

有時候,我們在網頁重構的時候,會發現,z-index巢狀錯亂,看看是不是受父級的層疊上下文元素干擾了。然後,可能沒多大意義了,但我還是提一下,算是祭奠下,IE6/IE7瀏覽器有個bug,就是z-index:auto的定位元素也會建立層疊上下文。這就是為什麼在過去,IE6/IE7的z-index會搞死人的原因。

然後,我再提一下position:fixed, 在過去,position:fixedrelative/absolute在層疊上下文這一塊是一路貨色,都是需要z-index為數值才行。但是,不知道什麼時候起,Chrome等webkit核心瀏覽器,position:fixed元素天然層疊上下文元素,無需z-index為數值。根據我的測試,目前,IE以及FireFox仍是老套路。

③. CSS3與新時代的層疊上下文
CSS3的出現除了帶來了新屬性,同時還對過去的很多規則發出了挑戰。例如,CSS3 transform對overflow隱藏對position:fixed定位的影響等。而這裡,層疊上下文這一塊的影響要更加廣泛與顯著。

如下:

  1. z-index值不為autoflex項(父元素display:flex|inline-flex).
  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.

基本上每一項都有很多槽點。

1. display:flex|inline-flex與層疊上下文
注意,這裡的規則有些負責複雜。要滿足兩個條件才能形成層疊上下文:條件1是父級需要是display:flex或者display:inline-flex水平,條件2是子元素的z-index不是auto,必須是數值。此時,這個子元素為層疊上下文元素,沒錯,注意了,是子元素,不是flex父級元素。

眼見為實,給大家上例子吧。

如下HTML和CSS程式碼:

<div class="box">
    <div>
    	<img src="mm1.jpg">
    </div>
</div>
.box {  }
.box > div { background-color: blue; z-index: 1; }    /* 此時該div是普通元素,z-index無效 */
.box > div > img { 
  position: relative; z-index: -1; right: -150px;     /* 注意這裡是負值z-index */
}

結果如下:

會發現,妹子跑到藍色背景的下面了。為什麼呢?層疊順序圖可以找到答案,如下:
負值z-index的層疊順序

從上圖可以看出負值z-index的層疊順序在block水平元素的下面,而藍色背景div元素是個普通元素,因此,妹子直接穿越過去,在藍色背景後面的顯示了。

現在,我們CSS微調下,增加display:flex, 如下:

.box { display: flex; }
.box > div { background-color: blue; z-index: 1; }    /* 此時該div是層疊上下文元素,同時z-index生效 */
.box > div > img { 
  position: relative; z-index: -1; right: -150px;     /* 注意這裡是負值z-index */
}

結果:

會發現,妹子在藍色背景上面顯示了,為什麼呢?層疊順序圖可以找到答案,如下:

從上圖可以看出負值z-index的層疊順序在當前第一個父層疊上下文元素的上面,而此時,那個z-index值為1的藍色背景<div>的父元素的display值是flex,一下子升官發財變成層疊上下文元素了,於是,圖片在藍色背景上面顯示了。這個現象也證實了層疊上下文元素是flex子元素,而不是flex容器元素。

另外,另外,這個例子也顛覆了我們傳統的對z-index的理解。在CSS2.1時代,z-index屬性必須和定位元素一起使用才有作用,但是,在CSS3的世界裡,非定位元素也能和z-index愉快地搞基。

2. opacity與層疊上下文
我們直接看程式碼,原理和上面例子一樣,就不解釋了。

如下HTML和CSS程式碼:

<div class="box">
    <img src="mm1.jpg">
</div>
.box { background-color: blue;  }
.box > img { 
  position: relative; z-index: -1; right: -150px;
}

結果如下:

然後價格透明度,例如50%透明:

.box { background-color: blue; opacity: 0.5;  }
.box > img { 
  position: relative; z-index: -1; right: -150px;
}

結果如下:

原因就是半透明元素具有層疊上下文,妹子圖片的z-index:-1無法穿透,於是,在藍色背景上面乖乖顯示了。

3. transform與層疊上下文
應用了transform變換的元素同樣具有選單上下文。

我們直接看應用後的結果,如下CSS程式碼:

.box { background-color: blue; transform: rotate(15deg);  }
.box > img { 
  position: relative; z-index: -1; right: -150px;
}

結果如下:

妹子同樣在藍色背景之上。

4. mix-blend-mode與層疊上下文
mix-blend-mode類似於PS中的混合模式,之前專門有文章介紹-“CSS3混合模式mix-blend-mode簡介”。

元素和白色背景混合。無論哪種模式,要麼全白,要麼沒有任何變化。為了讓大家有直觀感受,因此,下面例子我特意加了個原創平鋪背景:

.box { background-color: blue; mix-blend-mode: darken;  }
.box > img { 
  position: relative; z-index: -1; right: -150px;
}

結果如下:

需要注意的是,目前,IE瀏覽器(包括IE14)還不支援mix-blend-mode,因此,要想看到妹子在背景色之上,請使用Chrome或FireFox。

同樣的,因為藍色背景元素升級成了層疊上下文,因此,z-index:-1無法穿透,在藍色背景上顯示了。

5. filter與層疊上下文
此處說的filter是CSS3中規範的濾鏡,不是舊IE時代私有的那些,雖然目的類似。同樣的,我之前有提過,例如圖片的灰度或者圖片的毛玻璃效果等。

我們使用常見的模糊效果示意下:

.box { background-color: blue; filter: blur(5px);  }
.box > img { 
  position: relative; z-index: -1; right: -150px;
}

結果如下:

好吧,果然被你猜對了,妹子藍色床上躺著,只是你眼鏡摘了,看得有些不夠真切罷了。

6. isolation:isolate與層疊上下文
isolation:isolate這個宣告是mix-blend-mode應運而生的。預設情況下,mix-blend-mode會混合z軸所有層疊在下面的元素,要是我們不希望某個層疊的元素參與混合怎麼辦呢?就是使用isolation:isolate。由於一言難盡,我特意為此寫了篇文章:“理解CSS3 isolation: isolate的表現和作用”,解釋了其阻隔混合模式的原理,建議大家看下。

要演示這個效果,我需要重新設計下,如下HTML結構:

<img src="img/mm2.jpg" class="mode">
<div class="box">
    <img src="mm1.jpg">
</div>

CSS主要程式碼如下:

.mode {
  /* 豎妹子絕對定位,同時混合模式 */
  position: absolute; mix-blend-mode: darken;
}    
.box {
  background: blue;         
}
.box > img { 
  position: relative; z-index: -1;
}

結構如下:

會發現,橫妹子被混合模式了。此時,我們給妹子所在容器增加isolation:isolate,如下CSS所示:

.mode {
  /* 豎妹子絕對定位,同時混合模式 */
  position: absolute; mix-blend-mode: darken;
}    
.box {
  background: blue; isolation:isolate;         
}
.box > img { 
  position: relative; z-index: -1;
}

結果為:

會發現橫著的妹子跑到藍色背景上面了。這表明確實建立了層疊上下文。

7. will-change與層疊上下文
關於will-change,如果有同學還不瞭解,可以參見我之前寫的文章:“使用CSS3 will-change提高頁面滾動、動畫等渲染效能”。

都是類似的演示程式碼:

.box { background-color: blue; will-change: transform;  }
.box > img { 
  position: relative; z-index: -1; right: -150px;
}

結果如下:

果然不出所料,妹子上了藍色的背景。

七、層疊上下文與層疊順序

本文多次提到,一旦普通元素具有了層疊上下文,其層疊順序就會變高。那它的層疊順序究竟在哪個位置呢?

這裡需要分兩種情況討論:

  1. 如果層疊上下文元素不依賴z-index數值,則其層疊順序是z-index:auto可看成z:index:0級別;
  2. 如果層疊上下文元素依賴z-index數值,則其層疊順序由z-index值決定。

於是乎,我們上面提供的層疊順序表,實際上還是缺少其他重要資訊。我又花功夫重新繪製了一個更完整的7階層疊順序圖(同樣的版權所有,商業請購買,可得無水印大圖):

更完整的7階層疊順序圖

大家知道為什麼定位元素會層疊在普通元素的上面嗎?

其根本原因就在於,元素一旦成為定位元素,其z-index就會自動生效,此時其z-index就是預設的auto,也就是0級別,根據上面的層疊順序表,就會覆蓋inlineblockfloat元素。

而不支援z-index的層疊上下文元素天然z-index:auto級別,也就意味著,層疊上下文元素和定位元素是一個層疊順序的,於是當他們發生層疊的時候,遵循的是“後來居上”準則。

我們可以速度測試下:

<img src="mm1" style="position:relative">
<img src="mm2" style="transform:scale(1);">
<img src="mm2" style="transform:scale(1);">
<img src="mm1" style="position:relative">


會發現,兩者樣式一模一樣,僅僅是在DOM流中的位置不一樣,導致他們的層疊表現不一樣,後面的妹子趴在了前面妹子的身上。這也說明了,層疊上下文元素的層疊順序就是z-index:auto級別。

z-index值與層疊順序
如果元素支援z-index值,則層疊順序就要好理解些了,比較數值大小嘛,小盆友都會,本質上是應用的“誰大誰上”的準則。在以前,我們只需要關心定位元素的z-index就好,但是,在CSS3時代,flex子項也支援z-index,使得我們面對的情況比以前要負複雜。然而,好的是,規則都是一樣的,對於z-index的使用和表現也是如此,套用上面的7階層疊順序表就可以了。

同樣,舉個簡單例子,看下z-index:-1z-index:1變化對層疊表現的影響,如下兩段HTML:

<div style="display:flex; background:blue;">
   <img src="mm1.jpg" style="z-index:-1;">
</div>
<div style="display:flex; background:blue;">
   <img src="mm1.jpg" style="z-index:1;">
</div>

最後,會發現,z-index:-1跑到了背景色小面,而z-index:1高高在上。

一個與層疊上下文相關的有趣的顯示現象
在實際專案中,我們可能會漸進使用CSS3的fadeIn淡入animation效果增強體驗,於是,我們可能就會遇到類似下面的現象:

您可以狠狠地點選這裡:CSS3 fadeIn淡入animation動畫有趣現象

有一個絕對定位的黑色半透明層覆蓋在圖片上,預設顯示是這樣的:

文字在妹子上

但是,一旦圖片開始走fadeIn淡出的CSS3動畫,文字跑到圖片後面去了

文字跑到圖片後面

為什麼會這樣?

實際上,學了本文的內容,就很簡單了!fadeIn動畫本質是opacity透明度的變化:

@keyframes fadeIn {
  0% { 
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

要知道,opacity的值不是1的時候,是具有層疊上下文的,層疊順序是z-index:auto級別,跟沒有z-index值的absolute絕對定位元素是平起平坐的。而本demo中的文字元素在圖片元素的前面,於是,當CSS3動畫只要不是最終一瞬間的opacity: 1,位於DOM流後面的圖片就會遵循“後來居上”準則,覆蓋文字。

這就是原因,於是,我們想要解決這個問題就很簡單。

1. 調整DOM流的先後順序;
2. 提高文字的層疊順序,例如,設定z-index:1;

八、結束語

只要元素髮生層疊,要解釋其表現,基本上就本文的這些內容了。

我發現很多重構小夥伴都有z-index濫用,或者使用不規範的問題。我覺得最主要的原因還是對理解層疊上下文以及層疊順序這些概念都不瞭解。例如,只要使用了定位元素,尤其absolute絕對定位,都離不開設定一個z-index值;或者只要元素被其他元素覆蓋了,例如變成定位元素或者增加z-index值升級。頁面一複雜,必然搞得亂七八糟。

實際上,在我看來,覺得多數常見,z-index根本就沒有出現的必要。知道了內聯元素的層疊水平比塊狀元素高,於是,某條線你想覆蓋上去的時候,需要設定position:relative嗎?不需要,inline-block化就可以。因為IE6/IE7 position:relative會建立層疊上下文,很煩的。

OK,本文已經夠長了,就不多囉嗦了。

行為匆忙,出錯在所難免,歡迎大力指正。也歡迎各種形式的交流,或者指出文中概念性的錯誤。

感謝閱讀!

轉載於:https://www.zhangxinxu.com/wordpress/2016/01/understand-css-stacking-context-order-z-index/