html5 视差地图,用HTML5构建高性能视差网站的图文代码详解
本文介紹了一種時尚的網站設計方法,以及如何由淺入深的通過HTML5和瀏覽器渲染機制來構建高性能的站點。
文中多處涉及瀏覽器重繪和性能優化的原理,也是《Web滾動性能優化實戰》的拓展和延續,難度上屬于中級進階,請在閱讀前請先看看這篇文章。
介紹
視差網站最近風靡一時,只需看看下面這些站點:
Old Pulteney Row to the Pole
Adidas Snowboarding
BBC News - James Bond: Cars, catchphrases and kisses
如果你還不了解它們,它們其實就是頁面的視覺結構隨滾動變化的站點。正常情況下,頁面上的元素按比例縮放、旋轉或者移動到滾動的位置。
我們視差效果的演示頁面
你喜不喜歡視差網站是一回事,但是我們能確定的是這絕對是一個性能黑洞。原因是當你滾動時,瀏覽器會試圖對新內容出現的地方(根據滾動的方向)進行性能優化,總的來講,在滾動中視覺上越少更新瀏覽器性能越好。對于視差網站來說這很少見,因為在整個頁面上大的視覺元素會多次發生改變,從而導致瀏覽器必須對整個頁面進行重繪(為什么是性能黑洞,可以參考我的這篇文章《Web滾動性能優化實戰》)。
把視差網站歸納為下面的特性是合理的:
1、 當你向上或者向下滾動頁面時,背景元素改變位置、旋轉或者縮放。
2、 頁面內容,例如文本或者小圖片,以特別的從上到下的方式滾動。
我們之前介紹過滾動性能及其優化方式,你可以以此來改進應用的響應能力。本文將建立在此基礎上,所以你需要先讀一下上面這篇文章。
所以現在的問題是,如果你正在構建一個視差滾動網站,是必須要進行代價昂貴的重繪,還是有其它方法可以采用來最大限度的提高性能?讓我們來看看可供選擇的方法。
方法1:使用DOM元素和絕對定位
這可能是大多數人選擇的方式。頁面里有許多元素,當滾動事件觸發時,許多視覺上的更新會發生在這些元素上。這里我展示了一個演示頁面。
如果你開啟了開發者工具時間軸的frame模式,并且上下滾動,你會注意到有代價昂貴的全屏繪制操作。如果你滾動多次,你也許可以在一個單獨的幀里看到多個滾動事件,每一個都會觸發布局工作。
開發者工具展示了一幀里有大量的繪制操作和多個由事件觸發的布局
重要的是要牢記,為了達到60fps(與典型的顯示器刷新率60Hz相匹配),我們必須要在差不多16ms內完成所有事情。在這第一個版本中,我們每當得到一個滾動事件,我們就要執行一次視覺更新,但是正如我們在前面的文章-《用requestAnimationFrame實現更簡單動畫》和《Web滾動性能優化實戰》里討論到的一樣,這與瀏覽器的更新節奏并不一致。所以我們要么錯過幀,要么在一幀里完成太多的工作。這會讓你的站點很容易看起來不舒服和不自然,導致用戶感覺失望。
讓我們把視覺更新的代碼從滾動事件中移到requestAnimationFrame回調里,并且在滾動事件的回調里簡單的獲取滾動的值。我們在第二個演示中展示了這個變化。
如果你重復滾動測試,你可能會注意到有輕微的改善,雖然并不多。原因是由滾動觸發的布局操作代價昂貴,而現在我們只在每幀中執行一次布局操作。
開發者工具展示了一幀里有大量的繪制操作和多個由事件觸發的布局
我們現在在每幀里可以處理一個或者上百個滾動事件,但最重要的是,我們僅僅存儲最近的一個滾動值,供requestAnimationFrame回調觸發時使用,并執行視覺上的更新。關鍵是我們已經從每次接收到滾動事件時進行視覺更新優化為在瀏覽器給我們的合適時機進行處理。你是不是覺得這相當給力?
這個方法的主要問題是,無論使用requestAnimationFrame與否,我們基本上都會生成整個頁面的層,在移動這些視覺元素時需要大量和代價昂貴的重繪。通常重繪會是一個阻塞操作(雖然這點將會優化),這意味著瀏覽器不能同時進行其它工作,而我們經常有可能超過瀏覽器16ms的幀的處理時限,這代表會出現性能上卡頓的情況。
方法2:使用DOM元素和3D轉換
除了絕對定位之外,另外一種我們可以采用的方法就是3D轉換(transform)。在這種情況下我們可以看到每個用3D轉換處理的元素都會產生新的層。相比之下,在方法1中,如果有任何變化時,我們必須要重繪頁面上一大部分的層。
這意味著使用此方法情況會大為不同:我們可能對應用了3D轉換的任何元素都會有一個層。如果通過更多元素的轉換做到這一點,我們不需要重繪任何層,GPU能夠處理移動元素和合成整個頁面。也許你想知道為什么用3D轉換替代3D,原因是2D轉換不能保證得到一個新的層,而3D轉換可以。
這是另一個使用了3D轉換的演示。滾動時你可以看到性能已經大有改觀。
很多時候人們使用-webkit-transform:translateZ(0)這個技巧,能夠看到有奇妙的性能改善(宇捷注:關于這種方式,其實就是利用3D轉換來開啟瀏覽器硬件加速,屬于一種Hack。國內很少有資料提及,而國外有很多移動App開發性能優化的文章提到。國內可以看看《改善HTML5網頁性能》,國外可以看看《IncreasingPerformance of HTML and JavaScript on Mobile Devices》)。這種方式現在可以正常工作,但是會帶來一些問題:
1、 它并不是瀏覽器兼容的;
2、 它強迫瀏覽器為每一個轉換的元素創建新的層。大量的層會帶來其它性能瓶頸,所以需要有節制的使用。
3、 它在某些Webkit版本的移植上被禁用。
所以,你如果采用這種方法需要非常謹慎,這對解決問題來說是一個臨時方案。在完美的情況下我們甚至都不會考慮它,而且瀏覽器每天都在改進中,誰知道也許哪天我們就不需要它了。
方法3:使用固定定位(Fixed Position)的Canvas或者WebGL
我們最后要考慮的方法就是在頁面上采用固定定位的Canvas,而把轉換的圖像繪制在上面。乍看之下,這可能不是最高效的解決方案,但是它有幾個好處:
我們不再需要大量合成工作,因為頁面只有一個元素 - Canvas;
我們可以高效的通過硬件加速處理一個單獨的bitmap;
Canvas2D API非常適合我們要執行的轉換類型,這意味著開發和維護更容易管理。
使用Canvas元素為我們提供了一個新的層,但是它只有一層,而在方法2中我們為每一個應用3D轉換的元素都創建了一個新層,所以有額外的工作量來把這些層合成在一起。
如果你看看這種方法的演示,并且在開發者工具中觀察,你會發現它的性能更加優異。在這個方法里,我們只需在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的庫,你可以隨時回退為使用Canvas元素,同時你的代碼能以一種一致和友好的方式進行抽象。我們需要做的只是用Modernizr來檢測相應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,而不是自己處理上下文。這里有一個支持兩種渲染方式的演示。
這種方法的最后一個問題是,如果你并不特別愛好在頁面上添加額外的元素,你可以總是在Firefox和Webkit瀏覽器里使用canvas作為背景元素。很明顯,這并不是普遍適用的,所以你應該對此持謹慎態度。
逐步退化
開發者默認采用絕對定位元素而不是其它方法的主要原因可能僅僅簡單是瀏覽器支持的問題。這種方式在一定程度上是錯誤的,因為對于老舊的瀏覽器來說,只能提供非常貧乏的渲染體驗。即便在現代瀏覽器中,使用絕對定位也不一定能帶來好的性能。
更好的方案是在老舊的瀏覽器上避免嘗試視差效果,僅在最好的瀏覽器上確保能夠用正確的API呈現站點效果。當然,如果你使用了Three.js,你應該能夠很容易根據所需要的支持在渲染器之間進行切換。
結論
我們評估了幾種方式來處理大量重繪的區域,從絕對定位的元素到使用固定定位的Canvas。當然你要采用的實現方式,取決于你要達到的目標和具體設計,但是知道有多種選擇是一件好事情。在本文的例子中,我們設法從相對卡頓、低于30fps優化到了平滑、60fps的效果。
總結
以上是生活随笔為你收集整理的html5 视差地图,用HTML5构建高性能视差网站的图文代码详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 云计算实战应用案例精讲-【深度学习】多模
- 下一篇: 计算机领域nt=p,计算机考试范题-pw