? ? ? 本文介紹了一種時尚的網站設計方法,以及如何由淺入深的通過HTML5和瀏覽器渲染機制來構建高性能的站點。
? ? ??文中多處涉及瀏覽器重繪和性能優化的原理,也是《[Web滾動性能優化實戰](http://blog.csdn.net/hfahe/article/details/8539579)》的拓展和延續,難度上屬于中級進階,請在閱讀前請先看看這篇文章。
? ? ??**介紹**
? ? ??視差網站最近風靡一時,只需看看下面這些站點:
- ? ? ? ??[Old Pulteney Row to the Pole](http://www.rowtothepole.com/)
- ? ? ? ??[Adidas Snowboarding](http://www.adidas.com/com/apps/snowboarding/)
- ? ? ? ??[BBC News - James Bond: Cars, catchphrases and kisses](http://www.bbc.co.uk/news/entertainment-arts-20026367)
? ? ??如果你還不了解它們,它們其實就是頁面的視覺結構隨滾動變化的站點。正常情況下,頁面上的元素按比例縮放、旋轉或者移動到滾動的位置。

我們視差效果的演示頁面
? ? ??你喜不喜歡視差網站是一回事,但是我們能確定的是這絕對是一個性能黑洞。原因是當你滾動時,瀏覽器會試圖對新內容出現的地方(根據滾動的方向)進行性能優化,總的來講,在滾動中視覺上越少更新瀏覽器性能越好。對于視差網站來說這很少見,因為在整個頁面上大的視覺元素會多次發生改變,從而導致瀏覽器必須對整個頁面進行重繪(為什么是性能黑洞,可以參考我的這篇文章《[Web滾動性能優化實戰](http://blog.csdn.net/hfahe/article/details/8539579)》)。
? ? ??把視差網站歸納為下面的特性是合理的:
? ? ??1、? 當你向上或者向下滾動頁面時,背景元素改變位置、旋轉或者縮放。
? ? ??2、? 頁面內容,例如文本或者小圖片,以特別的從上到下的方式滾動。
? ? ??我們之前介紹過[滾動性能](http://blog.csdn.net/hfahe/article/details/8539579)及其優化方式,你可以以此來改進應用的響應能力。本文將建立在此基礎上,所以你需要先讀一下上面這篇文章。
? ? ??所以現在的問題是,如果你正在構建一個視差滾動網站,是必須要進行代價昂貴的重繪,還是有其它方法可以采用來最大限度的提高性能?讓我們來看看可供選擇的方法。
? ? ??**方法1:使用DOM元素和絕對定位**
? ? ??這可能是大多數人選擇的方式。頁面里有許多元素,當滾動事件觸發時,許多視覺上的更新會發生在這些元素上。這里我展示了一個[演示頁面](http://www.html5rocks.com/static/demos/parallax/demo-1a/demo.html)。
? ? ??如果你開啟了開發者工具時間軸的frame模式,并且上下滾動,你會注意到有代價昂貴的全屏繪制操作。如果你滾動多次,你也許可以在一個單獨的幀里看到多個滾動事件,每一個都會觸發布局工作。

開發者工具展示了一幀里有大量的繪制操作和多個由事件觸發的布局
? ? ??重要的是要牢記,為了達到60fps(與典型的顯示器刷新率60Hz相匹配),我們必須要在差不多16ms內完成所有事情。在這第一個版本中,我們每當得到一個滾動事件,我們就要執行一次視覺更新,但是正如我們在前面的文章-《[用requestAnimationFrame實現更簡單動畫](http://www.html5rocks.com/en/tutorials/speed/animations/)》和《[Web滾動性能優化實戰](http://www.html5rocks.com/en/tutorials/speed/scrolling/)》里討論到的一樣,這與瀏覽器的更新節奏并不一致。所以我們要么錯過幀,要么在一幀里完成太多的工作。這會讓你的站點很容易看起來不舒服和不自然,導致用戶感覺失望。
? ? ??讓我們把視覺更新的代碼從滾動事件中移到requestAnimationFrame回調里,并且在滾動事件的回調里簡單的獲取滾動的值。我們在[第二個演示](http://www.html5rocks.com/static/demos/parallax/demo-1b/demo.html)中展示了這個變化。
? ? ??如果你重復滾動測試,你可能會注意到有輕微的改善,雖然并不多。原因是由滾動觸發的布局操作代價昂貴,而現在我們只在每幀中執行一次布局操作。

開發者工具展示了一幀里有大量的繪制操作和多個由事件觸發的布局
? ? ??我們現在在每幀里可以處理一個或者上百個滾動事件,但最重要的是,我們僅僅存儲最近的一個滾動值,供requestAnimationFrame回調觸發時使用,并執行視覺上的更新。關鍵是我們已經從每次接收到滾動事件時進行視覺更新優化為在瀏覽器給我們的合適時機進行處理。你是不是覺得這相當給力?
? ? ??這個方法的主要問題是,無論使用requestAnimationFrame與否,我們基本上都會生成整個頁面的層,在移動這些視覺元素時需要大量和代價昂貴的重繪。通常重繪會是一個阻塞操作(雖然這點將會[優化](http://www.chromium.org/developers/design-documents/impl-side-painting)),這意味著瀏覽器不能同時進行其它工作,而我們經常有可能超過瀏覽器16ms的幀的處理時限,這代表會出現性能上卡頓的情況。
? ? ??**方法2:使用DOM元素和3D轉換**
? ? ??除了絕對定位之外,另外一種我們可以采用的方法就是3D轉換(transform)。在這種情況下我們可以看到每個用3D轉換處理的元素都會產生新的層。相比之下,在方法1中,如果有任何變化時,我們必須要重繪頁面上一大部分的層。
? ? ??這意味著使用此方法情況會大為不同:我們可能對應用了3D轉換的任何元素都會有一個層。如果通過更多元素的轉換做到這一點,我們不需要重繪任何層,GPU能夠處理移動元素和合成整個頁面。也許你想知道為什么用3D轉換替代3D,原因是2D轉換不能保證得到一個新的層,而3D轉換可以。
? ? ??這是另一個使用了3D轉換的[演示](http://www.html5rocks.com/static/demos/parallax/demo-2/demo.html)。滾動時你可以看到性能已經大有改觀。
? ? ??很多時候人們使用-webkit-transform:translateZ(0)這個技巧,能夠看到有奇妙的性能改善(宇捷注:關于這種方式,其實就是利用3D轉換來開啟瀏覽器硬件加速,屬于一種Hack。國內很少有資料提及,而國外有很多移動App開發性能優化的文章提到。國內可以看看《[改善HTML5網頁性能](http://www.doc88.com/p-307127263294.html)》,國外可以看看《[IncreasingPerformance of HTML and JavaScript on Mobile Devices](http://the.ichibod.com/kiji/increasing-performance-of-html-and-javascript-on-mobile-devices-especially-ios/)》)。這種方式現在可以正常工作,但是會帶來一些問題:
? ? ??1、? 它并不是瀏覽器兼容的;
? ? ??2、? 它強迫瀏覽器為每一個轉換的元素創建新的層。大量的層會帶來其它性能瓶頸,所以需要有節制的使用。
? ? ??3、? 它在某些[Webkit版本的移植](http://developer.apple.com/library/ios/#releasenotes/General/RN-iOSSDK-6_0/_index.html)上被禁用。
? ? ??所以,你如果采用這種方法需要非常謹慎,這對解決問題來說是一個臨時方案。在完美的情況下我們甚至都不會考慮它,而且瀏覽器每天都在改進中,誰知道也許哪天我們就不需要它了。
? ? ??**方法3:使用固定定位(Fixed Position)的Canvas或者WebGL**
? ? ??我們最后要考慮的方法就是在頁面上采用固定定位的Canvas,而把轉換的圖像繪制在上面。乍看之下,這可能不是最高效的解決方案,但是它有幾個好處:
- ? ? ? ? 我們不再需要大量合成工作,因為頁面只有一個元素 - Canvas;
- ? ? ? ? 我們可以高效的通過硬件加速處理一個單獨的bitmap;
- ? ? ? ? Canvas2D API非常適合我們要執行的轉換類型,這意味著開發和維護更容易管理。
? ? ??使用Canvas元素為我們提供了一個新的層,但是它只有一層,而在方法2中我們為每一個應用3D轉換的元素都創建了一個新層,所以有額外的工作量來把這些層合成在一起。
? ? ??如果你看看這種方法的[演示](http://www.html5rocks.com/static/demos/parallax/demo-3/demo.html),并且在開發者工具中觀察,你會發現它的性能更加優異。在這個方法里,我們只需在Canvas上調用drawImage API、設置背景圖像,以及每一個要在屏幕上正確位置繪制的色塊。
~~~
/**
* Updates and draws in the underlying visual elements to the canvas.
*/
function updateElements () {
var relativeY = lastScrollY / h;
// Fill the canvas up
context.fillStyle = "#1e2124";
context.fillRect(0, 0, canvas.width, canvas.height);
// Draw the background
context.drawImage(bg, 0, pos(0, -3600, relativeY, 0));
// Draw each of the blobs in turn
context.drawImage(blob1, 484, pos(254, -4400, relativeY, 0));
context.drawImage(blob2, 84, pos(954, -5400, relativeY, 0));
context.drawImage(blob3, 584, pos(1054, -3900, relativeY, 0));
context.drawImage(blob4, 44, pos(1400, -6900, relativeY, 0));
context.drawImage(blob5, -40, pos(1730, -5900, relativeY, 0));
context.drawImage(blob6, 325, pos(2860, -7900, relativeY, 0));
context.drawImage(blob7, 725, pos(2550, -4900, relativeY, 0));
context.drawImage(blob8, 570, pos(2300, -3700, relativeY, 0));
context.drawImage(blob9, 640, pos(3700, -9000, relativeY, 0));
// Allow another rAF call to be scheduled
ticking = false;
}
/**
* Calculates a relative disposition given the page’s scroll
* range normalized from 0 to 1
* @param {number} base The starting value.
* @param {number} range The amount of pixels it can move.
* @param {number} relY The normalized scroll value.
* @param {number} offset A base normalized value from which to start the scroll behavior.
* @returns {number} The updated position value.
*/
function pos(base, range, relY, offset) {
return base + limit(0, 1, relY - offset) * range;
}
/**
* Clamps a number to a range.
* @param {number} min The minimum value.
* @param {number} max The maximum value.
* @param {number} value The value to limit.
* @returns {number} The clamped value.
*/
function limit(min, max, value) {
return Math.max(min, Math.min(max, value));
}
~~~
? ? ? 這種做法真的在處理大圖片(或者其它很容易寫到一個Canvas上的元素)或者大塊的文本時肯定根據挑戰性。但是在你的網站上,它可能被證明會是最合適的解決方案。如果你不得不在Canvas上處理文本,你也許要使用fillText API,但是它有訪問成本(你剛剛才把文本轉換為bitmap!)而且你需要處理文本換行以及其它問題。你需要盡量避免這么做。
? ? ??討論了這么多,我們沒有理由假設視差的工作就一定要用Canvas元素。如果瀏覽器支持,我們可以使用WebGL。這里面的關鍵是WebGL是所有API到顯卡最直接的方式,并且在你的站點效果很復雜的情況下性能是最有可能達到60fps的。
? ? ??你最直接的反應可能是覺得采用WebGL矯枉過正,或者它并沒有獲得廣泛支持,但是如果你如果使用了類似于[Three.js](https://github.com/mrdoob/three.js/)的庫,你可以隨時回退為使用Canvas元素,同時你的代碼能以一種一致和友好的方式進行抽象。我們需要做的只是用[Modernizr](http://modernizr.com/)來檢測相應API的支持:
~~~
// check for WebGL support, otherwise switch to canvas
if (Modernizr.webgl) {
renderer = new THREE.WebGLRenderer();
} else if (Modernizr.canvas) {
renderer = new THREE.CanvasRenderer();
}
~~~
? ? ? 然后使用Three.js的API,而不是自己處理上下文。這里有一個支持兩種渲染方式的[演示](http://www.html5rocks.com/static/demos/parallax/demo-4/demo.html)。
? ? ??這種方法的最后一個問題是,如果你并不特別愛好在頁面上添加額外的元素,你可以總是在Firefox和Webkit瀏覽器里[使用canvas作為背景元素](http://updates.html5rocks.com/2012/12/Canvas-driven-background-images)。很明顯,這并不是普遍適用的,所以你應該對此持謹慎態度。
? ? ??**逐步退化**
? ? ??開發者默認采用絕對定位元素而不是其它方法的主要原因可能僅僅簡單是瀏覽器支持的問題。這種方式在一定程度上是錯誤的,因為對于老舊的瀏覽器來說,只能提供非常貧乏的渲染體驗。即便在現代瀏覽器中,使用絕對定位也不一定能帶來好的性能。
? ? ??更好的方案是在老舊的瀏覽器上避免嘗試視差效果,僅在最好的瀏覽器上確保能夠用正確的API呈現站點效果。當然,如果你使用了[Three.js](https://github.com/mrdoob/three.js/),你應該能夠很容易根據所需要的支持在渲染器之間進行切換。
? ? ??**結論**
? ? ??我們評估了幾種方式來處理大量重繪的區域,從絕對定位的元素到使用固定定位的Canvas。當然你要采用的實現方式,取決于你要達到的目標和具體設計,但是知道有多種選擇是一件好事情。在本文的例子中,我們設法從相對卡頓、低于30fps優化到了平滑、60fps的效果。
? ? ??與往常一樣,無論嘗試哪種方法,不要憑借猜測,而應該親自動手去嘗試。
? ? ? 像往常一樣,轉載請注明:來自蔣宇捷的博客(http://blog.csdn.net/hfahe)
? ? ??譯自:[http://www.html5rocks.com/en/tutorials/speed/parallax/](http://www.html5rocks.com/en/tutorials/speed/parallax/)
? ? ??相關文章:《[Web滾動性能優化實戰](http://blog.csdn.net/hfahe/article/details/8539579)》
- 前言
- AutoPager的簡單實現
- 利用CSS3特性巧妙實現漂亮的DIV箭頭
- IE9在Win7下任務欄新特性簡介
- 瀏覽器九宮格的簡單實現
- Raphael js庫簡介
- 使用CSS3構建Ajax加載動畫
- 用CSS3創建動畫價格表
- 用CSS3實現瀏覽器的縮放功能
- 用純CSS3實現QQ LOGO
- 用CSS3創建旋轉載入器
- 使用Javascript開發移動應用程序
- 用HTML5創建超酷圖像灰度漸變效果
- 使用CSS3創建文字顏色漸變(CSS3 Text Gradient)
- 僅用CSS創建立體旋轉幻燈片
- 如何創建跨瀏覽器的HTML5表單
- 用CSS3實現動畫進度條
- HTML5 Guitar Tab Player
- 奇妙的HTML5 Canvas動畫實例
- 談HTML5和CSS3的國際化支持
- 實現跨瀏覽器的HTML5占位符
- 前端開發必備工具:WhatFont Bookmarklet-方便的查詢網頁上的字體
- 使用HTML5和CSS3來創建幻燈片
- HTML5之美
- 如何使用HTML5創建在線精美簡歷
- 以小見大、由淺入深-談如何面試Javascript工程師
- 快速入門:HTML5強大的Details元素
- 用CSS3實現圖像風格
- HTML5視頻字幕與WebVTT
- 用純CSS3實現Path華麗動畫
- 用3個步驟實現響應式網頁設計
- 遇見CSS3濾鏡
- 關于CSS3濾鏡的碎念
- 用純CSS3繪制萌系漫畫人物動態頭像
- CSS3新的鼠標樣式介紹
- 用HTML5獻上愛的3D玫瑰
- 對HTML5 Device API相關規范的解惑
- 如何使用HTML5實現拍照上傳應用
- 2012第一季度國外HTML5移動開發趨勢
- HTML5新特性:范圍樣式
- 百度開發者大會-《用HTML5新特性開發移動App》PPT分享
- Chrome 19對于HTML5最新支持的動態:電池狀態API,全屏API,震動API,語音API
- 遇見Javascript類型數組(Typed Array)
- 用HTML5 Audio API開發游戲音樂
- 用HTML5實現人臉識別
- 用Javascript實現人臉美容
- Chrome 20對于HTML5最新支持的動態:顏色輸入,網絡信息API,CSS著色器
- 用HTML5實現手機搖一搖的功能
- 用HTML5實現iPad應用無限平滑滾動
- 用非響應式設計構建跨端Web App
- 了解SVG
- HTML5圖像適配介紹
- HTML5安全:內容安全策略(CSP)簡介
- HTML5安全:CORS(跨域資源共享)簡介
- 用CSS3 Region和3D變換實現書籍翻頁效果
- 談談移動App的思維誤區
- Chrome新特性:文件夾拖拽支持
- 《關注HTML5安全》
- HTML5安全風險詳析之一:CORS攻擊
- HTML5安全風險詳析之二:Web Storage攻擊
- HTML5圖像適配最新進展:響應式圖片規范草案
- HTML5移動Web App相關標準狀態及路線圖
- HTML5安全風險詳析之三:WebSQL攻擊
- Chrome引入WebRTC支持視頻聊天App
- HTML5安全風險詳析之四:Web Worker攻擊
- HTML5安全風險詳析之五:劫持攻擊
- HTML5安全風險詳析之六:API攻擊
- HTML5安全攻防詳析之七:新標簽攻擊
- 在iOS Safari中播放離線音頻
- 使用WebRTC實現遠程屏幕共享
- Firefox、Android、iOS遇見WebRTC
- HTML5光線傳感器簡介
- HTML5安全攻防詳析之八:Web Socket攻擊
- HTML5安全攻防詳析之完結篇:HTML5對安全的改進
- 激動人心!在網頁上通過語音輸入文字 - HTML5 Web Speech API介紹
- Web滾動性能優化實戰
- 用CSS3設計響應式導航菜單
- 用HTML5構建高性能視差網站
- 漫談@supports與CSS3條件規則
- HTML5下載屬性簡介
- 如何開發優秀的HTML5游戲?-迪斯尼《尋找奧茲之路》游戲技術詳解(一)
- 如何開發優秀的HTML5游戲?-迪斯尼《尋找奧茲之路》游戲技術詳解(二)
- 趨勢:Chrome為打包應用提供強大新特性
- 從HTML5移動應用現狀談發展趨勢
- 基于HTML5的Web跨設備超聲波通信方案