z-index和transform,你真的了解嗎?


z-indextransform是CSS中的屬性,但很少同學將二者聯系到一起,感覺他們八桿子打不上。事實真的是這樣嗎?如果你也不能確認,這篇文章就值得你花點時間閱讀。因為閱讀完了,你會有所收獲的。

堆疊上下文(Stacking Context)

在開始今天的主題之前,先得回憶一下CSS中的Stacking Context(堆疊上下文)。因為只有了解清楚了這個概念,才能更好的了解下面的內容。

任何HTML文檔默認的堆疊上下文都是<html>元素。因此,除非創建新的堆疊上下文。默認情況下,元素的堆疊順序相對於頁面內的其他元素。在一個未做堆疊順序更換的頁面中,其順序就是根據HTML中的元素出現的先后順序來決定,先出現的在底下,后出現的在頂部。用數字來表示的話是就1,2,3,4,...,n這樣的順序。

 

第二個div做了一個margin-top-50px,可以看到第二個div遮住了第一個div。那么怎么才能改變默認的堆疊順序呢?

先把結論給大家拋出來,在CSS中可以使用z-indextransform可以改變元素的堆疊順序。但也可能會導致一些奇怪的情況,比如具有較大的z-index的元素並不總是位於具有較低z-index元素的上方。比如,在一些情況之下,同時使用z-indextransform會讓z-index失效等。

CSS中會產生新的層情況還有很多種:

  • 當一個元素位於HTML文檔的最外層(<html>元素)

  • 當一個元素被定位了並且擁有一個z-index值(不為auto

  • 當一個元素被設置了opacitytransformsfilterscss-regionspaged 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,floatoffset這些屬性,可以控制元素在x軸和y軸上的表現。而z軸上的表現形式可以通過z-indextransform來控制。

如何控制z軸

前面也說了,控制z是通過z-indextransform來實現的。先簡單的了解一下這兩種控制z軸的方法。

通過z-index控制z軸,需要配合position屬性,且position的屬性值為relativeabsolutefixedsticky時。並且給z-index顯式的設置數值,數值越大,其層級越高。簡單點說,數值越高,元素越在頂上。

transform可以通過它的translateZ()來改變元素的層疊順序,其值越大,越在頂層,離屏幕越近。不過通過transform:translateZ()改變元素z軸的層級,必須在元素的父元素中顯示的設置transform-style: preserve-3d或者在transform中顯示的設置perspective()。如下所示:

上面的示例可能還不能明顯的說明translateZ()改變堆疊上下文z軸的順序,因為上面的代碼有position設置,那你要是覺得好奇,可以看下面這個示例。

示例左邊的元素是沒有設置translateZ,右邊的元素設置了translateZ

有關於z-indextransform更多的教程可以閱讀下面這些文章:

  •  

z-index 和 translate3d

特別聲明:接下來的內容挑選於@凹凸實驗室的《探究transform動畫元素的z-index》一文。此文章詳細講解了transformz-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層級渲染異常

在一些瀏覽器或設備上,當transformz-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中的translateZtranslate3d(),或許你都不會想到他們之間有這么多的故事,甚至更沒有想到在實際業務中通過transform來替代z-index來控制元素的z軸的順序。那么這篇文章介紹的就是這兩者之間的故事,以及如何通過transform來控制元素z軸的順序。如果文章講解的有不對之處,或者你碰到過更奇葩的現象,以及相關的解決方案,歡迎在下面的評論中與我們一起分享。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM