高效能視差

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 一樣,這項功能是「盡力而為」的。如果主執行緒忙於處理其他工作,捲動事件就不會立即傳送,這表示視差效果會消失。

錯誤:更新 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 - distance) / perspective。我們最可能希望視差元素顯示視差元素,但能以我們編寫的大小顯示,因此必須採用這種方式進行放大,而不是依原樣縮小。

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

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

這個方法的運作方式

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

不過,將透視值套用至捲動元素會影響這個程序,因為會變更支撐捲動轉換的矩陣。此時,捲動 300 像素時,子項可能只會移動 150 像素,具體取決於您選擇的 perspectivetranslateZ 值。如果元素的 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 存放區中找到。

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