高效能視差

Paul Lewis
Robert Flack
Robert Flack

無論喜歡還是討厭,癱瘓相關內容將繼續保持。謹慎使用時,可能會為網頁應用程式增加深度和巧妙。但問題在於,以高效能的方式實作視差效果可能並不容易。本文將討論一個效能卓越的解決方案,也同樣重要的是跨瀏覽器運作。

視差插圖。

重點摘要

  • 請勿使用捲動事件或 background-position 建立視差動畫。
  • 使用 CSS 3D 變形功能產生更準確的視差效果。
  • 若是行動版 Safari,請使用 position: sticky,確保能夠傳播視差效果。

如要使用置入式解決方案,請前往 UI Element Samples GitHub 存放區並擷取 Parallax helper JS!您可以在 GitHub 存放區中查看視差捲動器的即時示範

問題視差

首先,我們接著介紹兩種達到視差效果的常見方式,特別是 這些做法不適合我們的用途

錯誤:使用捲動事件

視差設定的重要要求是應捲動與捲動;只要頁面捲動位置發生每一項變更,視差元素的位置都必須更新。聽起來很簡單,但新式瀏覽器的重要機制卻是能夠以非同步方式運作。這適用於捲動事件,在特定情況下。在大多數瀏覽器捲動事件中,系統會以「盡力」的方式傳送,並不保證會在捲動動畫的每個影格上都傳送。

下列重要資訊可讓我們瞭解為何必須避免使用以 JavaScript 為基礎的解決方案,依據捲動事件來移動元素:JavaScript 無法保證視差問題會一直與頁面的捲動位置相同。在舊版的 Safari 中,捲動事件是在捲動結束時才傳送,因此無法顯示 JavaScript 的捲動效果。較新版本「會」在動畫播放期間傳送捲動事件,但「盡力」仍會與 Chrome 類似,這點與 Chrome 類似。如果主執行緒正忙於處理任何其他工作,捲動事件將無法立即傳送,這表示視差效果將遺失。

錯誤:正在更新 background-position

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

為了實現視差動作的承諾,我們會需要可當做加速屬性 (目前代表會保持轉換和不透明度) 的項目,以及無需仰賴捲動事件的項目。

3D 中的 CSS

Scott KellumKeith Clark 在使用 CSS 3D 達到視差動作方面,都進行了大量工作,而他們使用的技巧就是如此:

  • 設定內含 overflow-y: scroll (也可能是 overflow-x: hidden) 捲動的包含元素。
  • 至於同一個元素,請套用 perspective 值,以及設為 top left0 0perspective-origin
  • 為了讓該元素的子項在 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 的淨值以 1 為淨,表示已縮放。非常方便。

這個方法的運作方式

我們很快就會說明這個概念,請務必釐清這個做法的成因。捲動是有效的轉換,也是可以加速的原因;主要涉及在 GPU 上移動圖層。在一般捲動式中,如果沒有任何觀點,則在比較捲動元素及其子項時,捲動會 1:1 進行。如果將元素往下捲動 300px,其子項就會以相同的金額進行轉換:300px

不過,如果將透視值套用到捲動元素,就會影響到這個程序;這會改變固定捲動轉換背後的矩陣。現在,視您選擇的 perspectivetranslateZ 值而定,300 像素的捲動只能移動 150 像素。如果元素的 translateZ 值為 0,會依照原本的 1:1 捲動,但從 Z 角度從視角來源推送的子項會以不同的速率捲動!淨結果:視差動態。而且重要的是,這會做為瀏覽器內部捲動機器的自動處理,因此您不需要監聽 scroll 事件或變更 background-position

勇於創新之旅:Mobile 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 形式的說明,可讓元素在捲動期間將元素「固定」至可視區域頂端或特定父項元素。規格如同其中大多數,但包含一些有用的寶石:

乍看之下,這句話看似不代表很划算,但這個語句的重點在於指出元素的固定度,確切來說就是計算元素的黏著度:「偏移值會以參照至最靠近捲動方塊的祖系」。換句話說,固定式元素在套用任何其他轉換「之前」 (而非「之後」) 計算,才能顯示固定式元素移動到其他元素或可視區域的距離。這表示,與先前捲動範例非常相似,如果偏移值是以 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- 前置字串的版本),僅適用於行動版 Safari。
  • 這不只在 Edge 中「有效」而已。Edge 會嘗試在 OS 層級處理捲動作業,這通常是件好事,但在本例中, Edge 無法偵測捲動期間的視角變化。如要修正這個問題,您可以新增固定位置元素,因為這項操作似乎會將 Edge 切換至 非 OS 捲動方法,並確保其因應視角變更。
  • 「網頁上的內容數量龐大!」許多瀏覽器在決定網頁內容的大小時都會考量這一點,但可惜的是,Chrome 和 Safari 並未考量到觀點。因此,如果有 (例如,元素套用 3 倍的比例值) 時,即使該元素在套用 perspective 後大小為 1 倍,您還是會看到捲軸和其他類似效果。如要解決這個問題,您可以使用右下角 (使用 transform-origin: bottom right) 縮放元素,因為這會導致超大型元素成長為可捲動區域的「排除區域」(通常是左上方);可捲動區域絕不會讓您看到或捲動至排除區域的內容。

結論

請謹慎使用視差效果,是有趣的效果。如您所見,您可以使用高效能、捲動耦合和跨瀏覽器的方式實作。您需要稍微執行數學運算,以及少量的樣板才能達到所需效果,因此我們封裝了一個小型輔助程式庫和範例,您可以在 UI Element Samples GitHub 存放區中找到。

現在就開始試用吧!