關於z-index
的問題是很多程式設計師都不知道它是如何起作用的。說起來不難,但是大部分人並沒有花時間去看規範,這往往會照成嚴重的後果。
你不信?那就一起來看看下面的問題。
問題
在下面的HTML我們寫了3
個<div>
元素,然後每個<div>
元素裡面都有一個<span>
元素,每個<span>
元素都有個背景色,並且使用absolute
定位,為了能更清楚地看到z-index
的效果,我們寫了一些其他的樣式。第一個<span>
元素的z-index
值為1
,其他兩個沒有設定。
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<div> <span class="red">Red</span> </div> <div> <span class="green">Green</span> </div> <div> <span class="blue">Blue</span> </div> .red, .green, .blue { position: absolute; } .red { background: red; z-index: 1; } .green { background: green; } .blue { background: blue; } |
See the Pen Stacking Order (problem) by Philip Walton (@philipwalton) on CodePen.
然後挑戰來了: 嘗試把紅色的<span>
元素放到其他兩個元素後面,但是必須遵守下面的規則:
- 不能修改HTML的內容
- 不能增加或修改任何元素的
z-index
屬性 - 不能增加或修改任何元素的
position
屬性
想挑戰一些的話,就點選上面Codepen的Edit按鈕去嘗試一下吧。如果你不能做到,那就接著看下去。
解決方案
See the Pen Stacking Order (problem) by Philip Walton (@philipwalton) on CodePen.
解決方案很簡單,你只需要給紅色的<span>
標籤增加一個opacity
小於1
,像下面這樣:
1 2 3 |
div:first-child { opacity: .99; } |
如果你覺得不可思議了,不相信透明度會影響疊加順序,那麼恭喜你,即將學習新的技能,一開始看到我也不信。
接下來讓我們來摸索一番。
堆疊順序
z-index
看上去很簡單,z-index
值大的元素在z-index
值小的元素前面,對吧?但其實這只是z-index
的一部分用法。很多程式猿都覺得很簡單,沒有花太多時間去認真閱讀規則。
HTML中的每一元素都是在其他元素的前面或者後面。這是眾所周知的堆疊順序(Stacking Order),這條規則在w3c規範裡面說的很清楚,但我前面提到過了,大部分程式猿並不真正理解。
如果沒有涉及z-index
和position
屬性的話,那規則很簡單,堆疊順序就是元素在HTML中出現的順序。(當然如果你對行內元素使用負margin
的話,可能情況會複雜一些。)
加上position
屬性的話,就是所有定位了得元素在沒有被定位的元素前面。(一個元素被定位的意思這裡指的是它有一個position
屬性,但是不是static
,而是relative
,absolute
等)
再加上z-index
屬性,事情就變得有點詭異。首先z-index
值越大,越靠前。但是z-index
屬性只作用在被定位了的元素上。所以如果你在一個沒被定位的元素上使用z-index
的話,是不會有效果的。還有就是z-index
會建立一個堆疊的上下文(Stacking Contexts),我們可以理解為一個層。
堆疊上下文
同一個父元素下面的元素會受父元素的堆疊順序影響,所以堆疊上下文是我們理解z-index
和堆疊順序的關鍵。(下面為了簡化,我們稱堆疊上下文為層。)
每一個層都有唯一的根節點。當一個元素建立一個層,那麼它的所有子元素都會受到父元素的堆疊順序影響。意味著如果一個元素位於一個最低位置的層,那你z-index
設定得再大,它也不會出現在其它層元素的上面。
現在我們來說說什麼情況下會產生新的層:
- 當一個元素位於HTML文件的最外層(
<html>
元素) - 當一個元素被定位了並且擁有一個
z-index
值(不為auto) - 當一個元素被設定了
opacity
,transforms
,filters
,css-regions
,paged media
等屬性。
一二條規則,Web開發者都知道,雖然他們不一定知道怎麼描述
最後一條,是很多非w3c規範裡面的文章很少提到的。通常來講,如果一個CSS屬性需要做一些特效的話,它都會建立一個新的層。
影響堆疊順序的因素有很多,我推薦你去看w3c規範,這篇文章我們主要探討關於層的內容。
同一層裡面的堆疊順序
下面是同一層裡面的堆疊順序(從後到前):
- 層的根元素
- 被定位了得元素並且
z-index
值為負,相同z-index
的情況下,按照HTML元素的書寫順序排列,下面相同。 - 沒有被定位的元素
- 被定位的元素,並且
z-index
值為auto
- 被定位了的元素並且
z-index
值為正。
注意:z-index
值為負的元素比較特殊,他們會先被繪製,意味著他們可以出現在其他元素的後面,甚至出現在它的父元素後面。但是必要條件是該元素必須與父元素處於同一層,並且父元素不是這個層的根元素。一個很好的例子
理解了如何和什麼時候會產生一個新的層,那麼下次如果你遇到z-index
值設了很大,但是不起作用的話就去看看它的祖先是否產生了一個新的層。
總結
說了這麼多,我們來給之前的程式碼加上堆疊順序。
1 2 3 4 5 6 7 8 9 |
<div><!-- 1 --> <span class="red"><!-- 6 --></span> </div> <div><!-- 2 --> <span class="green"><!-- 4 --><span> </div> <div><!-- 3 --> <span class="blue"><!-- 5 --></span> </div> |
當我們設定了opacity
之後變成下面這樣。
1 2 3 4 5 6 7 8 9 |
<div><!-- 1 --> <span class="red"><!-- 1.1 --></span> </div> <div><!-- 2 --> <span class="green"><!-- 4 --><span> </div> <div><!-- 3 --> <span class="blue"><!-- 5 --></span> </div> |
紅色的<span>
從6
變成1.1
,我用’.’來標記它是新生成的層裡面的第一個元素。
最後我們來總結一下為什麼紅色的<span>
會去到下面: 一開始有兩個層,一個由根節點產生,一個由設定了z-index:1
並且position:absolute
的紅色<span>
產生。當我們設定了opacity
時,產生了第三個層,並且第三個層把紅色<span>
產生的層包裹了,意味著剛開始的z-index的作用域只在第三個層裡面。而所有的<div>
都沒有定位或者z-index,所以他們的堆疊順序按照HTML出現順序排列,於是第三個層就去到下面。