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
  • 當一個元素被設置了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-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的工作原理
  • 沒人告訴你關於z-index的一些事
  • 你對position的瞭解程度有多少?
  • 十步圖解CSS的position
  • CSS3 3D Transform
  • CSS3 2D Transform
  • Transform-style和Perspective屬性

z-indextranslate3d

特別聲明:接下來的內容挑選於@凹凸實驗室的《探究transform動畫元素的z-index》一文。此文章詳細講解了transform和z-index在一起使用將會發生的狀況。

在一次需求中,需要做出三張卡牌走馬燈式滾動的效果,由於在前面的一張卡牌需要擋住後面的卡牌,自然而然地就用 z-index 使前面的卡牌顯示在最上面,配以 transform 動畫讓“走馬燈”滾起來,在開發過程中,在 PC 側 Chrome 中表現良好,在本人手機瀏覽器中也表現良好,最後測試時卻發現,在微信客戶端或 QQ 客戶端中打開頁面出現問題,“走馬燈”滾動時,卡牌先通過transform 就位後,才把 z-index 設置較大的卡牌置於上面,感覺上非常的不流暢。

究其原因,發現這是某些瀏覽器的渲染規則,涉及到 stacking context 的概念,transform 的元素會創建新的 DOM,層級會在普通元素的上面,除了 transform ,還有哪些情況會創建新 stacking context呢?

下圖是對 transformopacity 的測試結果:
在這裏插入圖片描述
很明顯,紅色 div 都在綠色 div 上面了,說明真的有創建了個更高層級的 stacking context。再做進一步測試,我給兩組的div 都加了position:relative;z-index:1;,結果綠色的都在上面了,手機微信上也一樣,這能不能說明 z-index 對層級的影響大於 transformopacity 呢。

至於 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-axisy-axis,不妨先看個例子:
在這裏插入圖片描述
實際效果是,看不到它們,然後我們再設置 perspective201px,這時可以很明顯地看到,.box2 佔據了整個屏幕,而.box1 寬高約爲 200px,唯有設置 translate3d(0,0,0) 時,寬高才爲 100px

現在可以來理解下 perspectivetranslate3d 的關係,perspective 可以比作鏡頭和 DOM 的距離,實際上設置多少都沒影響,因爲它通過跟 z-axis 上的數值比例來影響樣式,它更像是一個刻度,而 translate3dz-axis 則表示了 DOM 和屏幕的距離。假定鏡頭跟屏幕的距離固定了,z-axis 越大,DOM 逐漸遠離屏幕,靠近鏡頭,這時 DOM 看起來也就越大,當 z-axis 大於或等於 perspective 時,DOM元素已經在我們鏡頭的後面了,所以也就看不到它了。
在這裏插入圖片描述
現在也就好理解爲什麼 perspectivetranslate3d 能夠影響 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-indextranslate3d一節中,我們也瞭解到了,有時候設置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軸的順序。如果文章講解的有不對之處,或者你碰到過更奇葩的現象,以及相關的解決方案,歡迎在下面的評論中與我們一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章