# 你需要了解的z-index世界
>原作者:doyoe
原文鏈接:http://blog.doyoe.com/2014/01/21/css/%E4%BD%A0%E9%9C%80%E8%A6%81%E4%BA%86%E8%A7%A3%E7%9A%84z-index%E4%B8%96%E7%95%8C/
## z-index的重要性
在我看來,`z-index` 給了我們日常工作中以極大的幫助,我們用它來定義元素的層疊級別(stack level)。受益于它,你能做Popup, DropDown, Tips, 圖文替換等等。
在開始本篇之前,或許我們要先了解一下關于z-index的基本信息。
## W3C這樣描述
每個元素都具有三維空間位置,除了水平和垂直位置外,還能在 “Z軸” 上層層相疊、排列。元素在 “Z軸” 方向上的呈現順序,由層疊上下文和層疊級別決定。
在文檔中,每個元素僅屬于一個層疊上下文。元素的層疊級別為整型,它描述了在相同層疊上下文中元素在 “Z軸” 上的呈現順序。
同一層疊上下文中,層疊級別大的顯示在上,層疊級別小的顯示在下,相同層疊級別時,遵循后來居上的原則,即其在HTML文檔中的順序。
不同層疊上下文中,元素呈現順序以父級層疊上下文的層疊級別來決定呈現的先后順序,與自身的層疊級別無關。
## z-index語法和應用
```
z-index: auto | <integer>
```
`z-index` 接受的屬性值為:關鍵字auto和整數,整數可以是負值(Firefox2.0及之前不支持負值)。
需要注意的是 `z-index` 雖然很給力,卻只能應用于定位元素(即設置了 `position` 屬性為非 `static` 值),其它情況下,`z-index` 將被忽略。
對于定位元素而言,`z-index` 意味著:
- 確定該元素在當前層疊上下文中的層疊級別。
- 確定該元素是否創建了一個新的局部層疊上下文。
##創建層疊上下文
在規范中說明:當某個元素的 `z-index` 未顯式定義或者被指定為 `auto` 時,該元素不會產生新的局部層疊上下文。也就是說它可以和兄弟,祖先,后輩元素處在同一個堆疊上下文中,它們被放在一起比較層疊級別,兒子可以蓋住祖先,父親也可以蓋住兒子,兒子甚至可以越過祖先,蓋住祖先的兄弟,在層疊上下文中,它們是并級的關系。來看這樣一個例子 `DEMO1`: [z-index與創建層疊上下文](http://demo.doyoe.com/css/z-index/stacking-context.htm)
值得高興的是,大部分瀏覽器都實現了這個特性;不過在IE6/7下,不論 `z-index` 值是否被顯式定義,都將產生新的局部層疊上下文,也就是說子元素不可以越過是定位元素的父親,子元素都處在新創建的局部層疊上下文中,只能在內部進行層疊級別的比較。
## 深入淺出
某區域內有個浮層提示或者下拉菜單,于是可能需要遮住該區域之下的區域。
### HTML
```html
<div class="a">
...
<div class="tips">我是一個簡陋的浮層提示</div>
</div>
<div class="b">
...
</div>
```
### CSS
```css
.a{position:relative;}
.tips{position:absolute;z-index:99;}
```
如上HTML/CSS代碼,很顯然,浮層 `tips` 將可以覆蓋在其父級元素 `a` 的兄弟元素 `b` 之上。
于是你的意圖得到實現,效果如下 `圖一`:
(圖一)
這是具體的實現例子 `DEMO2`: [z-index實現元素層疊](http://demo.doyoe.com/css/z-index/create-stacking-context-normal.htm)。
不過很顯然,從 `DEMO2` 來看,你依然無法準確的判斷出在各瀏覽器下,`tips` 能蓋住 `b` 是因為其父級的定位還是本身的定位。
但是我們可以做這樣一個測試,我們讓 `b` 也擁有定位,Code如下:
#### CSS
```css
.a{position:relative;}
.tips{position:absolute;z-index:99;}
.b{position:relative;}
```
這段代碼run完之后,就比較糾結了,你能得到的效果將會如下 `圖二`:
(圖二)
當然要給出具體實現 `DEMO3`: [驗證創建局部層疊上下文](http://demo.doyoe.com/css/z-index/create-stacking-context-ie6-7-bug.htm)。
首先,我們來解讀一下這個例子:因為 `a` 和 `b` 都是 `relative` 且沒有定義 `z-index` (等同于z-index:auto),根據后來居上的原則,此時 `b` 的層疊級別是要高于 `a` 的,意思就是說 `a` 是無法遮住 `b` 的。不過從 `DEMO3` 中,我們看到 `a` 的子元素 `tips` 遮住了 b,這就表示 `tips` 能越過它,所以可以判斷出 `a` 沒有創建新的局部層疊上下文。很明顯,這是完全吻合標準對此的定義。
不過這是在非IE6/7之下結果。在IE6/7下,我們看到 `tips` 并沒能遮住 `b`,也就是說 `tips` 無法越過父級,因為 `a` 創建了新的局部層疊上下文,而 `a` 的層疊級別又比 b 低,所以 `tips` 無法遮住 `b`,這也就是在IE6/7下常出現覆蓋Bug的根源。
結合 `DEMO2` 和 `DEMO3`,你能很肯定的得出以下結論:
- 當定位元素沒有顯式定義z-index值時,不會創建新的局部層疊上下文
- 子元素有可能和祖先的兄弟或者祖先兄弟的子元素處在同一個層疊上下文中
在實際工作中,有些情況可能是你沒注意或者已然存在的。比如你事先可能并不知道 `b` 也是定位元素,或者由于某些原因,你需要將其設置為定位元素,于是可能出現各種兼容問題。如果你不了解 `z-index` 是如何創建局部層疊上下文,且又沒注意到IE6/7的實現錯誤,那么處理起這樣的問題將會讓你深陷泥潭。
所以在實際的場景中,如果是為了相互覆蓋而設置為定位,那么顯式的定義 `z-index` 值,將可避免出現創建新局部層疊上下文差異。
如果需要越過祖先和其它區塊內部元素進行相互層疊,那么考慮IE6/7的情況,也應該盡量避免給父級元素進定位。
## opacity與層疊上下文
我們知道 `opacity` 屬性是用來設置元素不透明度的。但可能知道 `opacity` 和層疊上下文有關的不多,不過沒關系,這里我們簡單聊聊這個話題,有兩點必須注意:
- 當opacity值小于1時,該元素會創建新的局部層疊上下文,也就是說它可以和定位元素進行層疊層別比較
- 當opacity值小于1時,該元素擁有層疊級別且相當于z-index:0或auto,但不能定義 z-index ,除非本身是定位元素
簡單來說,當一個普通的元素定義了 `opacity` 的值小于1時(比如 opacity:.5),那么該元素的層疊級別將會高于普通元素,其效果類同于定位元素沒有顯式定義 `z-index` 的情況,唯一的區別是沒有顯式定義 `z-index` 的定位元素不會產生局部層疊上下文,而定義了 `opacity` 值小于1的元素會產生新的局部層疊上下文。
### opacity猜想
假定我們有 `a`, `b`, `c` 三個元素,它們相互層層覆蓋在一起,如果這時將 `a` 元素定義為 `opacity:.8`,你知道結果會怎樣嗎?
### HTML
```html
<div class="a">a</div>
<div class="b">b</div>
<div class="c">c</div>
```
### CSS
```css
.a,.b,.c{width:100px;height:100px;}
.a{opacity:.8;background:#999;}
.b{margin:-70px 0 0 30px;background:#090;}
.c{margin:-70px 0 0 60px;background:#f00;}
```
如果你看明白了我對于 `opacity` 與層疊上下文的描述,相信你可以猜到結果,是的,`a` 元素將會覆蓋 `b` 和 `c` 元素,雖然它在HTML文檔中出現在 `b` 和 `c` 之前,且不是定位元素。
必須看看具體的示例不是么?`DEMO4`: [opacity與局部層疊上下文猜想](http://demo.doyoe.com/css/z-index/create-stacking-context-by-opacity.htm)。
如果我們將 `b` 和 `c` 設置為定位元素,又將會如何呢?
### CSS
```css
.a,.b,.c{width:100px;height:100px;}
.a{opacity:.8;background:#999;}
.b{position:relative;margin:-70px 0 0 30px;background:#090;}
.c{position:relative;margin:-70px 0 0 60px;background:#f00;}
```
不急,我們可以接著看示例 `DEMO5`: [opacity與局部層疊上下文猜想2](http://demo.doyoe.com/css/z-index/create-stacking-context-by-opacity-2.htm)。
從 `DEMO4` 和 `DEMO5` 兩例,我們可以驗證:當一個普通元素定義了 `opacity` 為小于1的值時,該元素將像定位元素一樣擁有層疊級別,可以覆蓋普通元素,并且其層疊級別與未顯式定義 `z-index` 的定位元素一樣。
### opacity創建局部層疊上下文
與未顯式定義 `z-index` 的定位元素唯一不同的是 `opacity` 值小于1的元素會創建局部層疊上下文。
創建局部層疊上下文意味著什么,前文我們已經詳述過。所以不再贅述,這里只給一個示例用以驗證該特性。先奉上代碼:
### HTML
```html
<div class="a">a
<div class="d">d</div>
</div>
<div class="b">b</div>
<div class="c">c</div>
```
### CSS
```css
.a,.b,.c,.d{width:100px;height:100px;}
.a{opacity:.8;background:#999;}
.b{position:relative;margin:-70px 0 0 30px;background:#090;}
.c{position:relative;margin:-70px 0 0 60px;background:#f00;}
.d{position:absolute;z-index:99;height:50px;background:#090;}
```
你可以先看看具體結果 `DEMO6`: [opacity創建新局部層疊上下文](http://demo.doyoe.com/css/z-index/create-stacking-context-by-opacity-3.htm)。
你會發現雖然 `a` 的子元素 `d` 將 `z-index` 定義為99,但 `d` 仍然無法遮住 `b` 和 `c` 元素,這是因為 `a` 創建了新的局部層疊上下文,`d` 元素無法超越父級。
需要注意的是,此時就算 `a` 元素變成了定位元素,也不能改變其會創建新局部層疊上下文的命運,因為他設置了 `opacity:.8`。
按照我們前文所說,如果 `a` 沒有定義 `opacity:.8` ,但卻像 `b` 和 `c` 元素一樣設置了 `relative`,那么其子元素 `d` 將可以覆蓋 `b` 和 `c`,至于這個例子就不再奉上了,大家隨便寫個測試一下即可。
## 圖文替換
上述都是理論性的東西,相對枯燥,來個實際點的應用場景。
我們聊聊圖文替換的事,相對于使用較廣的方案如:縮進正/負值(正/負text-indent)、超小字體、margin溢出、padding溢出、line-height溢出、透明字體、display:none、visibility:hidden等方案而言,使用 `z-index` 負值的方案,有一些明顯的優勢:
- 無需考慮是否會有性能問題類同使用上述列舉中的前幾種方案(比如使用負縮進值-9999px,雖然此時文本被移到屏幕之外或者被裁減,但仍然會繪制一個寬9999px的盒子);
- 沒有像類似超小字體和透明字體一樣的方案會需要一些額外的hack;
- 不像display:none方案那樣有SEO欺騙嫌疑;
- 當圖片加載失敗時,可以顯示文字;
- and etc…
先來看看一個圖文替換的例子 `DEMO7`: [圖文替換實例](http://demo.doyoe.com/css/z-index/back-top.htm)。
在不同的網絡環境下,它的表現如下 `圖三`:
(圖三)
具體的Code很簡單:
### HTML
```html
<a href="#top" title="回到頂部"><span>TOP▲</span></a>
```
### CSS
```css
a,a span{display:inline-block;width:38px;height:38px;}
a{background:url(images/ico.png) no-repeat;}
a:hover{background-position:0 -39px;color:#fff;}
a span{position:relative;z-index:-1;background-color:#eee;}
a:hover span{background-color:#999;}
```
你會發現我們將 `span` 設置為了 `z-index:-1`,此時它的層疊級別將比正常的元素還要低,所以它可以被其父元素超鏈接a蓋住,從而在圖片正常載入時顯示父元素的背景圖,在網絡環境不好圖片載入有問題時,顯示自身。
很多時候,要實現一個需求可能有無數種解決方案,能夠適應情況越多的方案毫無疑問會脫穎而出,這就要求我們可以去更多的思考,而不是更多的拷貝。