高效能視差

Paul Lewis
羅伯特弗拉克
Robert Flack

無論喜歡或討厭它,絕無視聽內容仍舊存在。謹慎使用時,網頁應用程式的深度和細微之處可能會增加。然而,以有效率的方式實作視差可能會充滿挑戰。在本文中,我們將探討高效能的解決方案,而且同樣重要,適用於各種瀏覽器。

視差插圖。

重點摘要

  • 請勿使用捲動事件或 background-position 建立視差動畫。
  • 使用 CSS 3D 轉換功能建立更準確的視差效果。
  • 針對 Mobile Safari,請使用 position: sticky,確保視差效果生效。

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

問題視差

我們先來瞭解兩種常見的視差效果,特別是為何這些方法不適合我們的用途。

錯誤:使用捲動事件

視差的主要規定是,應捲動並疊顯示;對於頁面捲動位置的每次變更,視差元素的位置都應更新。雖然這聽起來很簡單,但新式瀏覽器的重要機制是非同步運作的能力。這種做法在特定情況下適用於捲動事件。在多數瀏覽器中,系統會「盡可能」提供捲動事件,並不保證會在捲動動畫的每個影格顯示!

這項重要資訊讓我們瞭解為何必須避免使用以 JavaScript 為基礎的解決方案,根據捲動事件移動元素:JavaScript 無法保證視差捲動可跟上頁面的捲動位置。在舊版的行動版 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 -距離) / view。因為我們最有可能希望視視元素呈現視差元素,但它會以我們編寫的大小呈現,因此必須放大,而非保持原樣。

在上述程式碼中,視角為 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

懷舊金曲: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 就會是 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 後該元素位於 1x,畫面上仍會出現捲軸和類似效果。如要解決這個問題,從右下角 (使用 transform-origin: bottom right) 縮放元素是可行的做法,因為這麼做會導致超大型元素進入可捲動區域的「排除區域」(通常是左上方);可捲動的區域絕不會讓您查看或捲動至排除區域中的內容。

結語

仔細使用時,視差是一種有趣的效果。如您所見,您可以採用高效能、可耦合且跨瀏覽器的方式實作,由於需要進行一些數學運算,以及使用少量樣板才能達到想要的效果,因此我們已包裝一個小型輔助程式庫和範例,您可以在 UI 元素範例 GitHub 存放區中找到。

立即暢玩遊戲,與我們分享你的想法。