z-index
和transform
是CSS中的屬性,但很少同學將二者聯繫到一起,感覺他們八杆子打不上。事實真的是這樣嗎?如果你也不能確認,這篇文章就值得你花點時間閱讀。因爲閱讀完了,你會有所收穫的。
堆疊上下文(Stacking Context)
在開始今天的主題之前,先得回憶一下CSS中的Stacking Context
(堆疊上下文)。因爲只有瞭解清楚了這個概念,才能更好的瞭解下面的內容。
任何HTML文檔默認的堆疊上下文都是<html>
元素。因此,除非創建新的堆疊上下文。默認情況下,元素的堆疊順序相對於頁面內的其他元素。在一個未做堆疊順序更換的頁面中,其順序就是根據HTML中的元素出現的先後順序來決定,先出現的在底下,後出現的在頂部。用數字來表示的話是就1,2,3,4,...,n
這樣的順序。
第二個div
做了一個margin-top
的-50px
,可以看到第二個div
遮住了第一個div
。那麼怎麼才能改變默認的堆疊順序呢?
先把結論給大家拋出來,在CSS中可以使用z-index
和transform
可以改變元素的堆疊順序。但也可能會導致一些奇怪的情況,比如具有較大的z-index
的元素並不總是位於具有較低z-index
元素的上方。比如,在一些情況之下,同時使用z-index
和transform
會讓z-index
失效等。
CSS中會產生新的層情況還有很多種:
- 當一個元素位於HTML文檔的最外層(
<html>
元素) - 當一個元素被定位了並且擁有一個
z-index
值(不爲auto
) - 當一個元素被設置了
opacity,transforms, filters, css-regions, paged media
等屬性 flex item
,也就是父元素的display
設置了flex
或者inline-flex
值,早期的box
值不行grid item
,也就是父元素的display
設置了grid
或者inline-grid
值isolation:isolate
- 元素的
mix-blend-mode
值不爲normal
- 元素的
overflow-scrolling
值不爲touch
- 元素的
filter
值不爲none
- 元素的
perspective
值不爲none
- 元素的
motion-path
值不爲none
三維空間
Web中的任何元素都存在於一個三維空間中,除了大家熟知的平面畫布中的x
軸和y
軸之外,還有控制第三維度的z
軸,如下圖所示:
在CSS中使用margin,float、offset
這些屬性,可以控制元素在x
軸和y
軸上的表現。而z軸上的表現形式可以通過z-index
和transform
來控制。
如何控制z軸
前面也說了,控制z是通過z-index
和transform
來實現的。先簡單的瞭解一下這兩種控制z
軸的方法。
通過z-index
控制z
軸,需要配合position
屬性,且position
的屬性值爲relative
、absolute
、fixed
和sticky
時。並且給z-index
顯式的設置數值,數值越大,其層級越高。簡單點說,數值越高,元素越在頂上。
transform
可以通過它的translateZ()
來改變元素的層疊順序,其值越大,越在頂層,離屏幕越近。不過通過transform:translateZ()
改變元素z軸的層級,必須在元素的父元素中顯示的設置transform-style: preserve-3d
或者在transform
中顯示的設置perspective()
。如下所示:
上面的示例可能還不能明顯的說明translateZ()
改變堆疊上下文z
軸的順序,因爲上面的代碼有position
設置,那你要是覺得好奇,可以看下面這個示例。
示例左邊的元素是沒有設置translateZ
,右邊的元素設置了translateZ
。
有關於z-index
和transform
更多的教程可以閱讀下面這些文章:
- z-index的工作原理
- 沒人告訴你關於
z-index
的一些事 - 你對
position
的瞭解程度有多少? - 十步圖解CSS的
position
- CSS3 3D Transform
- CSS3 2D Transform
- Transform-style和Perspective屬性
z-index
和 translate3d
特別聲明:接下來的內容挑選於@凹凸實驗室的《探究transform動畫元素的z-index》一文。此文章詳細講解了transform和z-index在一起使用將會發生的狀況。
在一次需求中,需要做出三張卡牌走馬燈式滾動的效果,由於在前面的一張卡牌需要擋住後面的卡牌,自然而然地就用 z-index
使前面的卡牌顯示在最上面,配以 transform
動畫讓“走馬燈”滾起來,在開發過程中,在 PC 側 Chrome 中表現良好,在本人手機瀏覽器中也表現良好,最後測試時卻發現,在微信客戶端或 QQ 客戶端中打開頁面出現問題,“走馬燈”滾動時,卡牌先通過transform
就位後,才把 z-index
設置較大的卡牌置於上面,感覺上非常的不流暢。
究其原因,發現這是某些瀏覽器的渲染規則,涉及到 stacking context
的概念,transform
的元素會創建新的 DOM,層級會在普通元素的上面,除了 transform
,還有哪些情況會創建新 stacking context
呢?
下圖是對 transform
和 opacity
的測試結果:
很明顯,紅色 div
都在綠色 div
上面了,說明真的有創建了個更高層級的 stacking context
。再做進一步測試,我給兩組的div
都加了position:relative;z-index:1;
,結果綠色的都在上面了,手機微信上也一樣,這能不能說明 z-index
對層級的影響大於 transform
和 opacity
呢。
至於 transform
變換的時候會讓 z-index
“臨時失效”,其實並非 z-index
失效了,只是 z-index
被用在不同的 stacking context
上,而非在默認的 context
上同等地比較層級了。所以 DOM 在 transform
的工程中,DOM 處於一個新的 stacking context
裏,z-index
也是相對於這個 stacking context
的,所以表現出來的實際是 stacking context
的層次,動畫一結束,DOM 又回到默認的 context
裏,這時的 z-index
纔是在同一個 context
上的比較。
那該用什麼方法來控制卡牌的層級,又能讓動畫流暢地表現呢,當然是 translate3d
中的 z-axis
,很多時候我們並不知道它是用來做什麼的,平常用得最多的只是它的 x-axis
和 y-axis
,不妨先看個例子:
實際效果是,看不到它們,然後我們再設置 perspective
爲 201px
,這時可以很明顯地看到,.box2
佔據了整個屏幕,而.box1
寬高約爲 200px
,唯有設置 translate3d(0,0,0)
時,寬高才爲 100px
。
現在可以來理解下 perspective
和 translate3d
的關係,perspective
可以比作鏡頭和 DOM 的距離,實際上設置多少都沒影響,因爲它通過跟 z-axis
上的數值比例來影響樣式,它更像是一個刻度,而 translate3d
的 z-axis
則表示了 DOM 和屏幕的距離。假定鏡頭跟屏幕的距離固定了,z-axis
越大,DOM 逐漸遠離屏幕,靠近鏡頭,這時 DOM 看起來也就越大,當 z-axis
大於或等於 perspective
時,DOM元素已經在我們鏡頭的後面了,所以也就看不到它了。
現在也就好理解爲什麼 perspective
和 translate3d
能夠影響 DOM 的層級了,它們在屏幕和鏡頭之間的距離不同,所以就有了層次,移動端設備很好地表現了這個結論,但在 PC 的 Chrome 上測試則不然,我們仍需要 z-index
纔會表現出我們需要的 層次關係。
transform
變換z-index
層級渲染異常
在一些瀏覽器或設備上,當transform
和z-index
在一起使用時會發生異樣,造成z-index
失靈。至於爲什麼會失靈,以及如何解決,這裏就不多講了。如果您對這方面的感興趣,可以看看@張鑫旭大師寫得一篇文章《Safari 3D transform變換z-index層級渲染異常的研究》。
文章總結了兩種解決方案:
- 方法1:父級,任意父級,非
body
級別,設置overflow:hidden
可恢復和其他瀏覽器一樣的渲染 - 方法2:以毒攻毒。也可以使用
3D transform
變換
至於怎麼使用3D Transform,大家還是移步看張大師是怎麼分析的。
何時使用Transform
來實現z-index
在介紹 z-index
和 translate3d
一節中,我們也瞭解到了,有時候設置z-index
來控制z
軸並不有效,張大師文章也提到過,它們在一起使用時,有時候會使用z-index
失靈。其實還有一個現象,大家可能平時並沒有注意到。
當你通過z-index
配合僞元素::before
或者::after
時讓其z
軸在元素的底部,特別是碰到大的元素渲染(比如全屏背景圖),會直接影響性能,特別是在移動端,會造成客戶端閃退,也就是大家所說的Crash,給用戶造成非常不好的體驗。
縮合上面的幾個現象(當然可能還有很多我自己沒有發現的),我們可以拋棄z-index
來控制z
軸的順序,而是直接通過transform
中的translateZ()
或者translate3d()
來控制z
軸的順序。
總結
單獨使用z-index
或者transform
中的translateZ
、translate3d()
,或許你都不會想到他們之間有這麼多的故事,甚至更沒有想到在實際業務中通過transform
來替代z-index
來控制元素的z
軸的順序。那麼這篇文章介紹的就是這兩者之間的故事,以及如何通過transform
來控制元素z
軸的順序。如果文章講解的有不對之處,或者你碰到過更奇葩的現象,以及相關的解決方案,歡迎在下面的評論中與我們一起分享。