無論你喜不喜歡,視差效果都會持續存在。只要運用得當,就能為網頁應用程式增添深度和細緻感。不過,以高效能方式實作平行移動效果可能相當困難。本文將討論一項兼具效能和跨瀏覽器相容性的解決方案。
重點摘要
- 請勿使用捲動事件或
background-position
建立視差動畫。 - 使用 CSS 3D 轉換功能,打造更精確的視差效果。
- 針對行動版 Safari,請使用
position: sticky
,確保視差效果會傳播。
如要使用即插即用解決方案,請前往 UI Element 範例 GitHub 存放區,並取得 Parallax 輔助 JS!您可以在 GitHub 存放區中查看視差捲動器的即時示範。
Parallaxers 問題
首先,我們來看看兩種常見的視差效果實作方式,並特別說明為何這些方式不適合用於我們的目的。
不當做法:使用捲動事件
視差效果的主要要求是必須與捲動連結,也就是說,每當網頁捲動位置有任何變更,視差效果元素的位置都應更新。雖然這聽起來很簡單,但新式瀏覽器的重要機制是其異步運作的能力。在我們的特殊情況中,這適用於捲動事件。在大多數瀏覽器中,捲動事件會以「盡力」方式傳送,且無法保證會在捲動動畫的每個影格中傳送!
這項重要資訊說明瞭為何我們需要避免使用以 JavaScript 為基礎的解決方案,因為這類解決方案會根據捲動事件移動元素:JavaScript 無法保證視差效果會與頁面的捲動位置保持同步。在舊版 Mobile Safari 中,捲動事件實際上是在捲動結束時傳送,因此無法製作以 JavaScript 為基礎的捲動效果。較新的版本會在動畫期間傳送捲動事件,但與 Chrome 一樣,這項功能是「盡力而為」的。如果主執行緒忙於執行其他工作,捲動事件就不會立即傳送,這表示視差效果會消失。
錯誤:更新 background-position
我們也建議您避免在每個影格上繪製。許多解決方案會嘗試變更 background-position
以提供視差效果,這會導致瀏覽器在捲動時重繪網頁受影響的部分,而這可能會造成動畫明顯的卡頓情形。
如要實現視差動畫效果,我們需要可做為加速屬性 (目前指的是轉換和不透明度) 的元素,且不依賴捲動事件。
3D 中的 CSS
Scott Kellum 和 Keith Clark 都曾在使用 CSS 3D 實現視差動畫方面做出重大貢獻,而他們使用的技巧如下:
- 設定包含元素,以便透過
overflow-y: scroll
(可能還有overflow-x: hidden
) 捲動。 - 將
perspective
值套用至相同元素,並將perspective-origin
設為top left
或0 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。由於我們很可能希望視差元素以視差效果顯示,但以我們建立的大小顯示,因此需要以這種方式放大,而非維持原樣。
在上述程式碼的情況下,透視效果為 1px,parallax-child
的 Z 距離為 -2px。這表示元素需要以 3 倍的比例放大,您可以看到這個值已插入程式碼:scale(3)
。
對於未套用 translateZ
值的任何內容,您可以改用零值。也就是說,比例為 (perspective - 0) / perspective,淨值為 1,表示比例未經過縮放。真的很方便。
這項方法的運作方式
請務必瞭解這項做法為何有效,因為我們很快就會運用這項知識。捲動畫面實際上是一種轉換,因此可以加速捲動畫面;這類轉換通常會涉及使用 GPU 移動圖層。在典型的捲動中 (沒有任何透視概念),比較捲動元素及其子項時,捲動會以 1:1 的方式進行。如果您將元素向下捲動 300px
,則其子項會向上轉換相同的數量:300px
。
不過,將透視值套用至捲動元素會影響這個程序,因為會變更支撐捲動轉換的矩陣。此時,捲動 300 像素時,子項可能只會移動 150 像素,具體取決於您選擇的 perspective
和 translateZ
值。如果元素的 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 存放區中找到。
請試玩一下,並告訴我們使用情形。