本文介紹了一種時尚的網站設計方法,以及如何由淺入深的透過HTML5和瀏覽器渲染機制來建立高效能的網站。
文中多處涉及瀏覽器重繪和性能優化的原理,也是《Web滾動性能優化實戰》的拓展和延續,難度上屬於中級進階,請在閱讀前請先看看這篇文章。
介紹
卷
#
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。 《IncreasingPerformance of HTML and JavaScript on Mobile Devices》)。這個方式現在可以正常運作,但是會帶來一些問題:
1、 它並不是瀏覽器相容的;
2、 它強迫瀏覽器為每一個轉換的元素建立新的層。大量的層會帶來其它性能瓶頸,所以需要有節制的使用。
3、 它在某些Webkit版本的移植上停用。
所以,你如果採取這種方法需要非常謹慎,這對解決問題來說是一個臨時方案。在完美的情況下我們甚至不會考慮它,而且瀏覽器每天都在改進中,誰知道也許哪天我們就不需要它了。
方法3:使用固定定位(Fixed Position)的Canvas或WebGL
## 我們最後要考慮的方法就是在頁面上採用固定定位的Canvas,而把轉換的圖像繪製在上面。乍看之下,這可能不是最有效率的解決方案,但它有幾個好處:使用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建構高效能視差網站的圖文程式碼詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!