高性能的視差動畫【譯】

高性能的視差動畫

原文地址: https://developers.google.com/web/updates/2016/12/performant-parallaxing
原文作者: Paul Lewis
譯文作者: 接灰的電子產(chǎn)品


愛也好,恨也好,視差效果已經(jīng)遍布web之上了。當(dāng)你用的巧妙的時候,它可以給應(yīng)用增加深度和隱喻效果。但問題在于實現(xiàn)一個高性能的視差效果是一個很有挑戰(zhàn)的工作。在這篇文章里,我們將討論如何構(gòu)造一個高性能的視差效果,當(dāng)然同樣重要的是還得跨瀏覽器。

視差效果示意圖
視差效果示意圖

摘要

  • 不要使用滾動事件(scroll events)或者背景定位(background-position)來創(chuàng)建視差動畫。
  • 使用 CSS 3D 變換來創(chuàng)建一個更準(zhǔn)確的視差效果。
  • 對于iOS移動設(shè)備的Safari瀏覽器使用 position: sticky 來確保視差可以生效。

如果你想要一個開箱即用的方案,請訪問 Parallax helper JS ,這里還有一個 demo演示

視差的問題分析

在開始之前,我們先來看兩個現(xiàn)有的常見的實現(xiàn)視差的方法,探討為何它們不適合我們要追求的目標(biāo)。

不好的方案:使用滾動事件

視差的關(guān)鍵需求是它應(yīng)該是滾動耦合的:對于頁面滾動的每一個位置變化,視差元素的位置也應(yīng)被更新。這看上去很容易,現(xiàn)代瀏覽器的重要機制之一就是它們可以異步處理工作。盡管如此,在大多數(shù)瀏覽器中,滾動事件是被作為“盡量好”(best effort)的工作處理的,也就意味著:瀏覽器并不確保每一幀的滾動動畫送達!

這個重要的信息告訴我們?yōu)槭裁匆苊馐褂肑avascript基于滾動事件去移動元素:Javascript并不能確保視差會和頁面滾動保持同樣的步調(diào)。。在一些老版本的Mobile Safari上,滾動事件甚至是在滾動完成后才觸發(fā)的,這一點讓基于Javascript的滾動效果無法實現(xiàn)。在較新的Mobile Safari版本中,滾動事件可以在動畫過程中觸發(fā)了,但是和Chrome一樣,它是一個基于“盡量好”的原則的。所以當(dāng)主線程忙于其他工作時,滾動事件不會立即觸發(fā),也就意味著視差效果的丟失。

不好的方案:更新背景位置

我們要避免的另一個場景是在每幀都進行繪制。很多方案試圖采用改變 background-position 來提供視差效果, 但這會讓瀏覽器在滾動時重繪受影響的部分,而這可能會是相當(dāng)消耗資源的,這種影響會使動畫卡頓。

如果我們想提供一個高質(zhì)量的視差動畫,我們想要的是一個可以當(dāng)作加速的屬性(這里我們指的是 transformopacity ),而這是不依賴于滾動事件的。

CSS 3D

Scott KellumKeith Clark 都已經(jīng)在利用 CSS 3D 來實現(xiàn)視差效果領(lǐng)域做出了很重要的工作。他們采用的非常有效技術(shù)有:

  1. 建立一個容器元素,設(shè)置 overflow-y: scroll 使其可以滾動(同時可能需要 overflow-x: hidden)。
  2. 對于上面的元素, 我們會應(yīng)用一個 perspective 值,然后設(shè)置 perspective-origintop left, 或者 0 0
  3. 對上面元素的子元素應(yīng)用一個在 Z 軸的變換,然后把它們還原回來以實現(xiàn)視差效果,而沒有影響它們在屏幕上的大小。

這種方案的 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);
}

當(dāng)然我們假定你的 HTML 是下面的樣子:

<div class="container”>
  <div class="parallax-child”></div>
</div>

調(diào)整perspective的比例

把子元素擠回來會要求它設(shè)置一個更小的相對于 perspective 的比例。你可以通過下列等式來計算需要的縮放比例: (perspective - distance) / perspective。由于我們希望視差元素看上去和我們一開始設(shè)定的一樣大,所以它應(yīng)該根據(jù)這樣的等式進行放縮而不是保持不變。

拿上面的例子來說, perspective1px, 而 parallax-child 在 Z 軸方向是 -2px,這就意味著元素需要被放大3倍,你可以看到我們在 scale 中寫入了 3 這個值。

對于任何沒有應(yīng)用 translateZ 的內(nèi)容,你可以用 0 替代,也就是縮放比為 (perspective - 0) / perspective,結(jié)果為 1 ,即既不放大也不縮小。真的是非常方便。

為什么這種方案好用?

弄清楚為什么這種方案好用是非常重要的,因為我們很快就要使用這個知識了。滾動其實本質(zhì)是一種變換,這是它為什么可以被加速的原因。滾動很大程度上使用GPU參與了圖層的變換。一個常見的滾動(沒有應(yīng)用任何 perspective )是這樣的:滾動這種情況下是以 1:1 的方式在對待滾動的元素和它的子元素。換人話說,如果你向下滾動一個元素 300px,那么它的子元素向上變換了同樣的數(shù)量: 300px

但是,如果對這個滾動元素應(yīng)用 perspective 值會把這個過程“搞亂”:這個值改變了滾動變換的理想路線。現(xiàn)在如果一個 300px 的滾動可能把子元素移動了 150px,當(dāng)然這取決于你給 perspectivetranslateZ 設(shè)置什么值。如果一個 translateZ 設(shè)置為0的子元素,它的滾動會一切如常 ( 1:1 ),但是一個被推向 Z 軸向( translateZ 不為 0 )的子元素將以不同的比例滾動!因此出現(xiàn)了視差效果。另外非常重要的一點是:這個過程本身就是瀏覽器內(nèi)部的滾動機制的一部分,因此沒有必要監(jiān)聽滾動事件或者改變背景位置。

美中不足:Mobile Safari

每種效果都有一些約束,對于變換( transform )來講,對子元素的 3D 效果的保持就是這樣。如果在應(yīng)用 perspective 的元素和它的“視差”子元素的結(jié)構(gòu)之中有其它元素的存在,那么 3D 的效果會被“拍扁”,也就是說效果將不復(fù)存在。

<div class="container”>
  <div class="parallax-container”>
    <div class="parallax-child”></div>
  </div>
</div>

在上面的HTML中 .parallax-container 是一個新添加的元素,而它會"抹平" perspective,從而導(dǎo)致效果丟失。通常情況下,解決方案還是比較符合直覺的:給這個元素添加 transform-style: preserve-3d 以便讓它可以把 3D 效果應(yīng)用到更深層的節(jié)點去。

.parallax-container {
  transform-style: preserve-3d;
}

對于 Mobile Safari 來說,事情變的有點麻煩。對容器元素應(yīng)用 overflow-y : 技術(shù)上這是沒問題的,但是這會讓滾動元素的移動過于兇猛。解決方案是加上一個 -webkit-overflow-scrolling: touch ,但這個設(shè)置會導(dǎo)致 perspective 被抹平,因此我們會得不到任何視差效果。

從一個發(fā)展的角度看,這可能算不上什么問題(因為只在舊版本的 Mobile Safari 出現(xiàn)),即使我們無法在每一個瀏覽器中展現(xiàn)視差效果,但一個應(yīng)用的功能還是好用的,但我們最好找出一個方案。

position:sticky 來拯救

事實上,我們可以從 position: sticky 中得到一些幫助,這個設(shè)置允許元素固定在 viewport 的頂部或者固定在一個滾動元素的父元素。這個屬性的文檔,就如同任何其它文檔一樣,又臭又長,但是還是可以找到一些有用信息:

一個固定的“盒子”非常像一個相對定位的盒子,但是位移是通過引用最近的可滾動的祖先來計算的,或者根據(jù) viewport 來計算,如果找不到這樣一個祖先的話 -- CSS Positioned Layout Module Level 3

第一眼看上去好像幫助不大,但一個關(guān)鍵點在句中說到如何計算元素的固定位置的那部分:“位移是通過引用最近的可滾動的祖先來計算的”。換句話說,移動固定元素的距離(為了讓它看起來是固定在某個元素或者 viewport 上)是在應(yīng)用其它任何變換之前進行計算的,而不是之后。這就意味著,和我們剛才說的滾動的那個例子很像,如果位移計算的結(jié)果是 300px,那么我們就有了一個新的機會去使用 perspective (或者其它任何變換)來在這個 300px 應(yīng)用到固定元素之前去改變它。

通過給視差元素設(shè)置 position: -webkit-sticky,我們可以有效的“翻轉(zhuǎn)”那個由于 -webkit-overflow-scrolling: touch 而產(chǎn)生的“抹平”效果。這樣就確保了視差元素引用最近的可滾動的祖先元素,這里就是 .container 。然后,和上文講的類似,給 .parallax-container 設(shè)置一個 perspective 值,這樣就改變了計算的滾動位移,創(chuàng)建出了視差效果。

<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 恢復(fù)了視差效果,真是一個不錯的結(jié)果。

固定定位的問題

和之前的方案確實還有一個明顯區(qū)別, position: sticky 改變了視差的機制。固定定位試圖固定某個元素在滾動容器頂端,而非固定元素不是。這就意味著固定定位產(chǎn)生的視差和非固定產(chǎn)生的色差是相反的:

  • 使用 position: sticky: 元素離 z=0 越近,它移動的越少
  • 不使用 position: sticky: 元素離 z=0 越近,它移動的越多

如果你還是感到有些抽象的話,可以看一下Robert Flack的這個demo,這個demo展示了在固定定位和非固定定位的條件下,元素是如何有不同的表現(xiàn)的。要看到這個效果的話,你需要 Chrome Canary (寫作本文是,版本為56) 或者 Safari 。

 `position: sticky` 對視差的影響
`position: sticky` 對視差的影響

花式bug和應(yīng)對

如同任何事情一樣,還是有很多的坑需要填。

  • 固定定位的支持是不一致的:當(dāng)前在 Chrome 中對于這個特性還在開發(fā)中,Edge 則完全缺失,F(xiàn)ireFox則有繪制的bug。在這種情況下,我們應(yīng)該增加一點代碼來在需要時(這里就是 Mobile Safari )才添加 position: sticky
  • 該效果在 Edge 中完全沒有作用。Edge試圖在OS級別處理滾動,通常情況下這是個好事。但在這個例子中,這種機制會使得我們無法在滾動時去應(yīng)用 perspective。為了修復(fù)這個問題,我們可以為父元素 設(shè)置 `translateZ(0px)``。
  • 頁面內(nèi)容太大了:在決定頁面內(nèi)容有多大時,很多瀏覽器負責(zé)放縮,但很遺憾 Chrome 和 Safari 不負責(zé)。所以假如有一個放大 3 倍的變換應(yīng)用到某個元素時,你可能會看到滾動條出現(xiàn)了,而且一旦出現(xiàn)后,即使之后你恢復(fù)了 1:1 的比例,滾動條也不會消失。有一個方法可以避免這種情況:那就是從右下角進行放縮( transform-origin: bottom right )。這種方案背后的原理是它會導(dǎo)致過大的元素進入滾動區(qū)域的“負面”(一般是左上),而滾動區(qū)域永遠不會讓你看到“負面”區(qū)域。

結(jié)論

視差動畫如果經(jīng)過的周全的設(shè)計考慮后會是一個非常有趣的效果。而且現(xiàn)在你應(yīng)該可以了解到我們是可以實現(xiàn)一個高性能的、滾動耦合的、跨瀏覽器的方案。由于這里面需要一點點數(shù)學(xué)計算和一些模板化的操作,所以我們封裝了一個工具類例子

歡迎試用,并提出您的寶貴意見。

慕課網(wǎng) Angular 視頻課上線: http://coding.imooc.com/class/123.html?mc_marking=1fdb7649e8a8143e8b81e221f9621c4a&mc_channel=banner

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補...
    _Yfling閱讀 13,786評論 1 92
  • CSS3動畫應(yīng)用很廣,尤其是在H5項目中,炫酷的交互效果可以給產(chǎn)品帶來更好的體驗,更能吸引用戶。然而在應(yīng)用的時候,...
    UIleader閱讀 2,236評論 0 7
  • 看了很多視頻、文章,最后卻通通忘記了,別人的知識依舊是別人的,自己卻什么都沒獲得。此系列文章旨在加深自己的印象,因...
    DCbryant閱讀 1,883評論 0 4
  • 執(zhí)筆丹青,為誰描摹那煙雨朦朧的江南晚景?佛一袖清塵,散落幾許回憶,眷戀那縷縷幽香。田園小徑,滿地殘紅,已不知...
    橙情閱讀 164評論 0 1
  • 親愛的卡蘭德拉: 吾愛 此時的我正身處一個叫爐石旅店的地方,我不確定這個旅店的位置是在哪里,但我相信它就在艾澤拉斯...
    白羊先生閱讀 403評論 0 4