原文地址: developers.google.com/web/updates…
原文做者: Paul Lewis
譯文做者: 接灰的電子產品javascript
愛也好,恨也好,視差效果已經遍及web之上了。當你用的巧妙的時候,它能夠給應用增長深度和隱喻效果。但問題在於實現一個高性能的視差效果是一個頗有挑戰的工做。在這篇文章裏,咱們將討論如何構造一個高性能的視差效果,固然一樣重要的是還得跨瀏覽器。css
position: sticky
來確保視差能夠生效。若是你想要一個開箱即用的方案,請訪問 Parallax helper JS ,這裏還有一個 demo演示。html
在開始以前,咱們先來看兩個現有的常見的實現視差的方法,探討爲什麼它們不適合咱們要追求的目標。java
視差的關鍵需求是它應該是滾動耦合的:對於頁面滾動的每個位置變化,視差元素的位置也應被更新。這看上去很容易,現代瀏覽器的重要機制之一就是它們能夠異步處理工做。儘管如此,在大多數瀏覽器中,滾動事件是被做爲「儘可能好」(best effort)的工做處理的,也就意味着:瀏覽器並不確保每一幀的滾動動畫送達!git
這個重要的信息告訴咱們爲何要避免使用Javascript基於滾動事件去移動元素:Javascript並不能確保視差會和頁面滾動保持一樣的步調。。在一些老版本的Mobile Safari上,滾動事件甚至是在滾動完成後才觸發的,這一點讓基於Javascript的滾動效果沒法實現。在較新的Mobile Safari版本中,滾動事件能夠在動畫過程當中觸發了,可是和Chrome同樣,它是一個基於「儘可能好」的原則的。因此當主線程忙於其餘工做時,滾動事件不會當即觸發,也就意味着視差效果的丟失。github
咱們要避免的另外一個場景是在每幀都進行繪製。不少方案試圖採用改變 background-position
來提供視差效果, 但這會讓瀏覽器在滾動時重繪受影響的部分,而這可能會是至關消耗資源的,這種影響會使動畫卡頓。web
若是咱們想提供一個高質量的視差動畫,咱們想要的是一個能夠看成加速的屬性(這裏咱們指的是 transform
和 opacity
),而這是不依賴於滾動事件的。chrome
Scott Kellum 和 Keith Clark 都已經在利用 CSS 3D 來實現視差效果領域作出了很重要的工做。他們採用的很是有效技術有:瀏覽器
overflow-y: scroll
使其能夠滾動(同時可能須要 overflow-x: hidden
)。perspective
值,而後設置 perspective-origin
到 top left
, 或者 0 0
。這種方案的 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
的比例。你能夠經過下列等式來計算須要的縮放比例: (perspective - distance) / perspective。因爲咱們但願視差元素看上去和咱們一開始設定的同樣大,因此它應該根據這樣的等式進行放縮而不是保持不變。
拿上面的例子來講, perspective
是 1px
, 而 parallax-child
在 Z 軸方向是 -2px
,這就意味着元素須要被放大3倍,你能夠看到咱們在 scale
中寫入了 3
這個值。
對於任何沒有應用 translateZ
的內容,你能夠用 0 替代,也就是縮放比爲 (perspective - 0) / perspective
,結果爲 1 ,即既不放大也不縮小。真的是很是方便。
弄清楚爲何這種方案好用是很是重要的,由於咱們很快就要使用這個知識了。滾動其實本質是一種變換,這是它爲何能夠被加速的緣由。滾動很大程度上使用GPU參與了圖層的變換。一個常見的滾動(沒有應用任何 perspective
)是這樣的:滾動這種狀況下是以 1:1
的方式在對待滾動的元素和它的子元素。換人話說,若是你向下滾動一個元素 300px
,那麼它的子元素向上變換了一樣的數量: 300px
。
可是,若是對這個滾動元素應用 perspective
值會把這個過程「搞亂」:這個值改變了滾動變換的理想路線。如今若是一個 300px
的滾動可能把子元素移動了 150px
,固然這取決於你給 perspective
和 translateZ
設置什麼值。若是一個 translateZ
設置爲0的子元素,它的滾動會一切如常 ( 1:1
),可是一個被推向 Z 軸向( translateZ
不爲 0 )的子元素將以不一樣的比例滾動!所以出現了視差效果。另外很是重要的一點是:這個過程自己就是瀏覽器內部的滾動機制的一部分,所以沒有必要監聽滾動事件或者改變背景位置。
每種效果都有一些約束,對於變換( transform
)來說,對子元素的 3D 效果的保持就是這樣。若是在應用 perspective
的元素和它的「視差」子元素的結構之中有其它元素的存在,那麼 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
: 技術上這是沒問題的,可是這會讓滾動元素的移動過於兇猛。解決方案是加上一個 -webkit-overflow-scrolling: touch
,但這個設置會致使 perspective
被抹平,所以咱們會得不到任何視差效果。
從一個發展的角度看,這可能算不上什麼問題(由於只在舊版本的 Mobile Safari 出現),即便咱們沒法在每個瀏覽器中展示視差效果,但一個應用的功能仍是好用的,但咱們最好找出一個方案。
position:sticky
來拯救事實上,咱們能夠從 position: sticky
中獲得一些幫助,這個設置容許元素固定在 viewport
的頂部或者固定在一個滾動元素的父元素。這個屬性的文檔,就如同任何其它文檔同樣,又臭又長,可是仍是能夠找到一些有用信息:
一個固定的「盒子」很是像一個相對定位的盒子,可是位移是經過引用最近的可滾動的祖先來計算的,或者根據
viewport
來計算,若是找不到這樣一個祖先的話 -- CSS Positioned Layout Module Level 3
第一眼看上去好像幫助不大,但一個關鍵點在句中說到如何計算元素的固定位置的那部分:「位移是經過引用最近的可滾動的祖先來計算的」。換句話說,移動固定元素的距離(爲了讓它看起來是固定在某個元素或者 viewport
上)是在應用其它任何變換以前進行計算的,而不是以後。這就意味着,和咱們剛纔說的滾動的那個例子很像,若是位移計算的結果是 300px
,那麼咱們就有了一個新的機會去使用 perspective
(或者其它任何變換)來在這個 300px
應用到固定元素以前去改變它。
經過給視差元素設置 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的這個demo,這個demo展現了在固定定位和非固定定位的條件下,元素是如何有不一樣的表現的。要看到這個效果的話,你須要 Chrome Canary (寫做本文是,版本爲56) 或者 Safari 。
如同任何事情同樣,仍是有不少的坑須要填。
position: sticky
perspective
。爲了修復這個問題,咱們能夠爲父元素 設置 `translateZ(0px)``。1:1
的比例,滾動條也不會消失。有一個方法能夠避免這種狀況:那就是從右下角進行放縮( transform-origin: bottom right
)。這種方案背後的原理是它會致使過大的元素進入滾動區域的「負面」(通常是左上),而滾動區域永遠不會讓你看到「負面」區域。視差動畫若是通過的周全的設計考慮後會是一個很是有趣的效果。並且如今你應該能夠了解到咱們是能夠實現一個高性能的、滾動耦合的、跨瀏覽器的方案。因爲這裏面須要一點點數學計算和一些模板化的操做,因此咱們封裝了一個工具類和例子。
歡迎試用,並提出您的寶貴意見。