無論你喜歡還是討厭,視差效果都會繼續存在。如果運用得當,可為網頁應用程式增添深度和細膩感。不過,以高效能方式實作視差效果可能相當困難。在本文中,我們將討論效能優異的解決方案,而且同樣重要的是,這項解決方案適用於跨瀏覽器。

重點摘要
- 請勿使用捲動事件或
background-position
建立視差動畫。 - 使用 CSS 3D 轉換,建立更精確的視差效果。
- 如果是使用 Mobile Safari,請使用
position: sticky
確保視差效果會傳播。
如要使用隨插即用的解決方案,請前往 UI Element Samples GitHub 存放區,並取得 Parallax 輔助 JS!您可以在 GitHub 存放區中查看視差捲動器的即時示範。
問題視差
首先,我們來看看兩種常見的視差效果實作方式,並特別說明為何不適合我們的用途。
不當做法:使用捲動事件
視差效果的關鍵要求是必須與捲動動作連結,也就是說,每當網頁的捲動位置發生變化,視差元素的位置就應更新。這聽起來很簡單,但現代瀏覽器的一項重要機制是能夠非同步運作。以我們的特定案例來說,這適用於捲動事件。在大多數瀏覽器中,捲動事件會以「盡力」的方式傳送,且無法保證會在捲動動畫的每個影格中傳送!
這項重要資訊說明瞭我們為何需要避免使用 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>
調整透視比例
將子項元素往後推,會導致元素依透視值比例縮小。您可以使用以下公式計算需要放大的程度:(透視 - 距離) / 透視。由於我們最有可能希望視差元素視差,但以我們撰寫的大小顯示,因此需要以這種方式放大,而不是保持原樣。
以上述程式碼為例,透視為 1 像素,而 parallax-child
的 Z 距離為 -2 像素。也就是說,元素必須放大 3 倍,您可以看到這是插入程式碼的值:scale(3)
。
如果內容未套用 translateZ
值,你可以代入零值。也就是說,比例為 (透視 - 0) / 透視,結果為 1,表示未放大或縮小。非常實用。
這個方法的運作方式
請務必清楚瞭解這項做法的原理,因為我們很快就會運用這項知識。捲動實際上是一種轉換,因此可以加速;這主要是指使用 GPU 轉移圖層。在一般捲動 (即沒有任何透視概念的捲動) 中,比較捲動元素及其子項時,捲動會以 1:1 的方式進行。如果您將元素向下捲動 300px
,子項也會向上變形相同的量:300px
。
不過,對捲動元素套用透視值會干擾這個程序,因為這會變更捲動轉換的基礎矩陣。現在,根據您選擇的 perspective
和 translateZ
值,300 像素的捲動可能只會將子項移動 150 像素。如果元素具有 0 的 translateZ
值,系統會以 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;
}
不過,如果是 Mobile Safari,情況會稍微複雜一點。
將 overflow-y: scroll
套用至容器元素在技術上可行,但代價是無法甩動捲動元素。解決方法是新增 -webkit-overflow-scrolling: touch
,但這也會將 perspective
扁平化,我們不會獲得任何視差效果。
從漸進式強化角度來看,這可能不是太大的問題。如果無法在所有情況下使用視差效果,應用程式仍可運作,但找出解決方法會更好。
position: sticky
來救援!
事實上,我們提供 position: sticky
形式的協助,讓元素在捲動期間「黏貼」至檢視區塊頂端或指定父項元素。這項規格與大多數規格一樣相當龐大,但其中包含實用的小訣竅:
乍看之下,這句話似乎沒有什麼特別之處,但其中一個重點是,它說明瞭系統如何計算元素的黏滯性:「系統會參照具有捲動方塊的最近上層元素,計算偏移量」。換句話說,系統會先計算移動固定元素所需的距離 (以便將元素附加至其他元素或檢視區塊),再套用任何其他轉換。也就是說,與先前的捲動範例非常相似,如果偏移量是在 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);
}
這項更新會為 Mobile Safari 恢復視差效果,對所有使用者來說都是好消息!
固定位置注意事項
不過,這裡有一個差異:position: sticky
會改變視差機制。黏性定位會嘗試將元素「黏」到捲動容器,而非黏性版本則不會。也就是說,具有黏性結尾的視差效果會與沒有黏性結尾的視差效果相反:
position: sticky
元素越靠近 z=0,移動的幅度就越小。- 沒有
position: sticky
,元素越靠近 z=0,移動的幅度就越大。
如果覺得有點抽象,請觀看 Robert Flack 的這個示範,瞭解元素在有和沒有固定定位時的行為差異。如要查看差異,您需要使用 Chrome Canary (撰寫本文時為 56 版) 或 Safari。

Robert Flack 的示範,說明
position: sticky
如何影響視差捲動。
各種錯誤和解決方法
不過,就像其他事物一樣,仍有需要解決的難題:
- 固定支援不一致。Chrome 仍在實作支援功能,Edge 完全不支援,而 Firefox 則在將 sticky 與透視轉換結合時,發生繪圖錯誤。在這種情況下,建議您新增一小段程式碼,只在需要時新增
position: sticky
(-webkit-
前置字元版本),也就是僅適用於 Mobile Safari 時。 - 效果不會在 Edge 中「正常運作」。Edge 會嘗試在作業系統層級處理捲動作業,這通常是好事,但在本例中,這會導致 Edge 無法偵測捲動期間的透視變化。如要修正這個問題,可以新增固定位置元素,因為這似乎會將 Edge 切換至非 OS 捲動方法 ,並確保系統會考量透視變化。
- 「頁面內容變得好大!」許多瀏覽器在決定網頁內容大小時,都會將比例納入考量,但很遺憾的是,Chrome 和 Safari 不會將透視效果納入考量。因此,如果元素套用了 3 倍的比例,即使在套用
perspective
後元素為 1 倍,您仍可能會看到捲軸等項目。如要解決這個問題,可以從右下角 (使用transform-origin: bottom right
) 縮放元素,因為這樣會導致過大的元素擴展到可捲動區域的「負區域」(通常是左上角);可捲動區域絕不會讓您查看或捲動至負區域中的內容。
結論
視差效果若運用得當,會非常有趣。如您所見,實作方式可兼顧效能、捲動耦合和跨瀏覽器。由於這需要一些數學運算,而且需要少量樣板才能達到所需效果,因此我們封裝了一個小型輔助程式庫和範例,您可以在 UI 元素範例 GitHub 存放區中找到。
歡迎試試看,並告訴我們你的使用體驗。