高效能視差

Paul Lewis
Robert Flack
Robert Flack

無論喜歡還是討厭,癱瘓相關內容將繼續保持。只要使用得當,就能為網頁應用程式增添深度和細緻感。不過,以高效能方式實作平移效果可能會相當困難。本文將討論一項兼具效能和跨瀏覽器相容性的解決方案。

視差插圖。

重點摘要

  • 請勿使用捲動事件或 background-position 建立視差動畫。
  • 使用 CSS 3D 轉換功能,打造更精確的視差效果。
  • 如果是行動版 Safari,請使用 position: sticky,確保能夠傳播視差效果。

如要使用即插即用解決方案,請前往 UI Element 範例 GitHub 存放區,並取得 Parallax 輔助 JS!您可以在 GitHub 存放區中查看視差捲動器的即時示範

Parallaxer 問題

首先,我們來看看兩種常見的視差效果實作方式,並特別說明為何這些方式不適合用於我們的用途。

不當做法:使用捲動事件

視差效果的主要要求是必須與捲動連結,也就是說,每當網頁捲動位置有任何變更,視差效果元素的位置都應更新。雖然這聽起來很簡單,但新式瀏覽器的重要機制是其異步運作的能力。這適用於捲動事件,在特定情況下。在大多數瀏覽器中,捲動事件會以「盡力」方式傳送,且無法保證會在捲動動畫的每個影格中傳送!

下列重要資訊可讓我們瞭解為何必須避免使用基於 JavaScript 的解決方案,以根據捲動事件來移動元素: JavaScript 不保證視差會隨著頁面的捲動位置而改變。在舊版 Mobile Safari 中,捲動事件實際上是在捲動結束時傳送,因此無法製作以 JavaScript 為基礎的捲動效果。較新版本「會」在動畫播放期間傳送捲動事件,但「盡力」仍會與 Chrome 類似,這點與 Chrome 類似。如果主執行緒忙於處理其他工作,捲動事件就不會立即傳送,這表示視差效果會消失。

錯誤:正在更新 background-position

我們也建議您避免在每個影格上繪製。許多解決方案都會嘗試變更 background-position,以提供視差樣式,讓瀏覽器在捲動時重新繪製頁面上受影響的部分,進而導致動畫卡頓。

如要實現視差動畫效果,我們需要可做為加速屬性 (目前指的是轉換和不透明度) 的元素,且不依賴捲動事件。

3D 中的 CSS

Scott KellumKeith Clark 都曾在使用 CSS 3D 實現視差動畫方面做出重大貢獻,而他們使用的技巧如下:

  • 設定內含 overflow-y: scroll (也可能是 overflow-x: hidden) 捲動的包含元素。
  • perspective 值套用至相同元素,並將 perspective-origin 設為 top left0 0
  • 針對該元素的子項,在 Z 軸上套用平移,並將其縮放回原大小,以提供視差動畫,而不影響其在螢幕上的大小。

這個方法的 CSS 如下所示:

.container {
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: scroll;
  perspective: 1px;
  perspective-origin: 0 0;
}

.parallax-child {
  transform-origin: 0 0;
  transform: translateZ(-2px) scale(3);
}

假設有一個類似下方的 HTML 程式碼片段:

<div class="container">
    <div class="parallax-child"></div>
</div>

調整透視縮放比例

將子項元素推回,會使該元素比例減少與視角值。您可以使用這個方程式計算需要擴大的幅度:(perspective -是從) / spec。由於我們很可能希望視差元素以視差效果顯示,但以我們建立的大小顯示,因此需要以這種方式放大,而非維持原樣。

在上述程式碼的情況下,透視效果為 1pxparallax-child 的 Z 距離為 -2px。這表示元素需要向上縮放 3 倍,您可以看到程式碼中插入的值:scale(3)

對於未套用 translateZ 值的任何內容,您可以改用零值。也就是說,比例為 (perspective - 0) / perspective,淨值為 1,表示比例既未縮小也未放大。真的很方便。

這個方法的運作方式

我們很快就會說明這項知識,請務必釐清這個做法的成因。捲動畫面實際上是一種轉換,因此可以加速捲動畫面;這類轉換通常會涉及使用 GPU 移動圖層。在典型的捲動中 (沒有任何透視概念),比較捲動元素及其子項時,捲動會以 1:1 的方式進行。如果您將元素向下捲動 300px,則其子項會向上轉換相同的數量:300px

不過,將透視值套用至捲動元素會影響這個程序,因為會變更支撐捲動轉換的矩陣。現在,視您選擇的 perspectivetranslateZ 值而定,300 像素的捲動只能移動 150 像素。如果元素的 translateZ 值為 0,則會以 1:1 的速度捲動 (與以往相同),但在 Z 軸上推送的子項如果離開透視原點,則會以不同的速度捲動!淨結果:視差動態。而且重要的是,這會做為瀏覽器內部捲動機器的自動處理,因此您不需要監聽 scroll 事件或變更 background-position

行動版 Safari 的缺點

每個效果都有相關的警告,其中一個重要的警告是,轉換效果會保留子元素的 3D 效果。如果元素間的階層結構中,有視透視子項和視差子項之間的元素,3D 視角就會「扁平化」,表示效果遺失。

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>

在上述 HTML 中,.parallax-container 是新的,它會有效地將 perspective 值平坦化,並讓我們失去視差效果。在大多數情況下,解決方案相當簡單:您只要將 transform-style: preserve-3d 新增至元素,即可讓元素傳播任何已在樹狀結構中更高層級套用的 3D 效果 (例如透視值)。

.parallax-container {
  transform-style: preserve-3d;
}

不過,在行動版 Safari 的情況下,情況就比較複雜。從技術層面來說,將 overflow-y: scroll 套用至容器元素是可行的,但這樣做會導致無法快速捲動捲動元素。解決方案是新增 -webkit-overflow-scrolling: touch,但這也會使 perspective 變平,我們不會看到任何視差。

從漸進式強化觀點來看,這個問題可能不是太多的問題。如果我們無法在所有情況下使用視差效果,應用程式仍可正常運作,但我們很樂意找出解決方法。

position: sticky 救星出動!

事實上,position: sticky 的形式可提供一些協助,讓元素在捲動期間「固定」在檢視區頂端或特定父項元素上。這項規格與大多數規格一樣相當龐大,但其中包含一個實用的寶貴資源:

乍看之下,這可能看似是個划算的交易,但這個語句的重點在於指出元素的固定度,確切來說是「偏移值的計算方式:透過捲動方塊參照最接近的祖系」。換句話說,系統會在套用任何其他轉換的,而不是之後,計算移動固定元素的距離 (以便讓該元素顯示為附加至其他元素或 viewport)。也就是說,就像先前的捲動範例一樣,如果偏移量計算結果為 300 像素,則在將 300 像素偏移量值套用至任何固定元素之前,您可以使用觀點 (或任何其他轉換) 來操控該值。

position: -webkit-sticky 套用至視差元素,即可有效「反轉」-webkit-overflow-scrolling: touch 的平坦效果。這可確保視差元素透過捲動方塊 (在本例中為 .container) 參照最近的祖系。接著,與先前相同,.parallax-container 會套用 perspective 值,藉此變更計算的捲動偏移量,並產生視差效果。

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>
.container {
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
}

.parallax-container {
  perspective: 1px;
}

.parallax-child {
  position: -webkit-sticky;
  top: 0px;
  transform: translate(-2px) scale(3);
}

這個動作會還原行動版 Safari 的視差效果,這真是好消息!

固定式定位的注意事項

不過,這裡差異:position: sticky 變更視差效果機制。固定位置會嘗試將元素固定在捲動容器中,而非固定版本則不會。也就是說,具有固定端的視差效果會與不具固定端的視差效果相反:

  • 使用 position: sticky 時,元素越接近 z=0,移動的距離就會越
  • 如果「不使用」position: sticky,則較靠近元素會移至 z=0,則會移動「更多」。

如果看起來較為抽象,請觀看 Robert Flack 提供的示範影片,瞭解各元素在固定位置和未固定位置時的行為有何差異。如要查看差異,您需要使用 Chrome Canary (撰寫本文時的版本為 56) 或 Safari。

視差透視圖螢幕截圖

Robert Flack 的示範:顯示 position: sticky 如何影響視差捲動。

各種錯誤和解決方法

不過,如同任何事情一樣,仍有需要處理的突起和凹陷:

  • 固定功能不一致。Chrome 仍在實作支援功能,Edge 完全不支援,而 Firefox 在將固定元素與透視轉換結合時會發生繪圖錯誤。在這種情況下,建議您加入一些程式碼,以便在需要時只新增 position: sticky (-webkit- 前置字元版本),這僅適用於 Mobile Safari。
  • 這不只在 Edge 中「有效」而已。Edge 會嘗試在作業系統層級處理捲動作業,這通常是件好事,但在這種情況下,這會導致 Edge 無法在捲動期間偵測透視圖變化。如要修正這個問題,您可以新增固定位置元素,因為這項操作似乎會將 Edge 切換至 非 OS 捲動方法,並確保其因應視角變更。
  • 「網頁上的內容數量龐大!」許多瀏覽器在決定網頁內容大小時會考量到縮放比例,但很遺憾,Chrome 和 Safari 不會考量到視角。因此,如果有 (例如,元素套用 3 倍的比例值) 時,即使該元素在套用 perspective 後大小為 1 倍,您還是會看到捲軸和其他類似效果。您可以透過從右下角縮放元素 (使用 transform-origin: bottom right) 來解決這個問題,因為這會導致過大元素擴展至捲動區域的「負面區域」(通常是左上方);捲動區域永遠不會讓您查看或捲動至負面區域中的內容。

結論

請謹慎使用視差效果,是有趣的效果。如您所見,可以以效能高、與捲動畫面相關聯,以及跨瀏覽器的方式實作。由於這項操作需要一點數學技巧,以及少量程式碼模板才能獲得所需效果,因此我們已包裝一個小型輔助程式庫和範例,您可以在 UI 元素範例 GitHub 存放區中找到。

請試玩看看,並告訴我們使用情形。