簡介
看來視差網站是近期有各種憤怒的內容,請看看這些網站:
如果你不熟悉這些機制,就必須瞭解捲動頁面時,頁面視覺結構的變動。通常是頁面縮放頁面中的元素,會根據捲動位置按比例旋轉或移動。
無論你是否喜歡提供視差網站,只要確保網站成效良好,都很安心。原因在於,當您捲動畫面時,如果畫面的頂端或底部會顯示新內容 (視捲動方向而定),瀏覽器通常會針對新內容進行最佳化調整。一般而言,瀏覽器最好在捲動畫面時只稍微調整外觀,才能發揮最佳效能。以視差網站來說,由於整個網頁上有許多大量視覺元素都會變更,導致瀏覽器會重新繪製整個網頁,因此這種情況很少見。
將視差網站的一般化為合理的,如下所示:
- 上下捲動時可變更其位置、旋轉和縮放的背景元素。
- 網頁內容 (例如文字或小型圖片) 會以一般由上而下的方式捲動。
我們先前提過捲動效能,並介紹如何改善應用程式回應速度。本文是以這些基礎為基礎,如果您還沒閱讀的話,建議您閱讀這項說明。
那麼,打造視差捲動網站時,可能會發現成本高昂,還是有其他可盡量提高效能的方法?接著來看看這些選項。
方法 1:使用 DOM 元素和絕對位置
這是大多數人採用的預設做法。該頁面會有許多元素,而每當捲動事件觸發一系列視覺更新時,即會轉換元素。
如果在影格模式中啟用開發人員工具時間軸,並在畫面四處捲動,就會發現全螢幕繪製作業耗用大量資源,而捲動大量事件時,在單一影格中可能會看到多個捲動事件,且每個事件都會觸發版面配置的工作。
切記,如果要達到 60fps (與一般的螢幕刷新率是 60 Hz 相等),我們只需超過 16 毫秒就能完成所有工作。在第一版中,我們每次收到捲動事件時都會執行視覺更新,但如先前文章中提到,更精簡的動畫動畫 (使用 requestAnimationFrame) 和捲動效能,不會與瀏覽器的更新時間表發生衝突,因此會遺漏影格或在各版本中執行過多工作。這種做法很容易對網站產生卡頓和不自然的感覺,導致使用者感到失望和不愉快的小貓。
請將更新程式碼從捲動事件移出 requestAnimationFrame
回呼,然後直接在捲動事件的回呼中擷取捲動值。
如果您重複執行捲動測試,可能會發現有些微改善,但不會大幅改善。這是因為捲動觸發的版面配置作業並非都耗費大量資源,但在其他用途中可能很常見。現在至少我們在每個影格中都只執行「一項版面配置」作業。
我們現在可以處理每個影格一或一百個捲動事件,但重要的是,只有在 requestAnimationFrame
回呼執行並執行視覺更新時,才會儲存最新的值。每當您收到捲動事件,要求瀏覽器提供適當視窗以便進行視覺更新時,就不是嘗試強制更新視覺設計。你不甜嗎?
這個方法的主要問題在於,requestAnimationFrame
基本上就是整個頁面都只有一個圖層。移動這些視覺元素時,我們必須重新繪製大量 (且費用高昂)。一般來說,繪畫屬於阻塞作業 (但這是變化),代表瀏覽器無法執行任何其他工作,而我們經常超出影格預算 16 毫秒,且畫面會出現資源浪費的情形。
選項 2:使用 DOM 元素和 3D 轉換
我們不採用絕對位置,而是將 3D 轉換套用至元素。在此情況下,我們會看到套用 3D 轉換的元素,為每個元素指定新的圖層,而在 WebKit 瀏覽器中,通常也會造成切換使用硬體合成器。相較之下,在選項 1 中,我們為頁面建立了一個大層,以供在發生任何變更時重新繪製的網頁,而所有繪製和合成都是由 CPU 處理。
換句話說,如果使用這個選項,則情況不同:我們會為套用 3D 轉換效果的任何元素設定一個圖層。在這個階段進行的所有操作如果更能處理元素的轉換,就不需重新繪製圖層,GPU 可以處理移動元素的位置,也可以一起組合最終頁面。
很多時候,使用者只會運用 -webkit-transform: translateZ(0);
入侵手法,看到效能明顯的提升,但目前這種攻擊方式還是存在一些問題:
- 因為這項功能不支援跨瀏覽器。
- 這會強制瀏覽器為每個轉換元素建立新圖層,大量層可能會造成其他效能瓶頸,因此請謹慎使用!
- 這個網站已停用部分 WebKit 通訊埠 (底部第四個項目符號!)。
如果沿著 3D 平移路徑切個謹慎,這只是暫時性的解決方案!在理想情況下,2D 轉換的成像會像和 3D 一樣的成像特徵相近。瀏覽器日新月異,希望您能在我們眼前見!
最後,建議您盡可能避免繪製顏料,只要移動頁面的現有元素即可。舉例來說,視差網站使用固定高度 div 並變更背景位置來達到效果,這是典型的做法。遺憾的是,元素每次傳遞都必須重新繪製,造成效能上的損失。如果可以,請改為建立元素 (必要時,使用 overflow: hidden
在 div 中包裝),並直接進行翻譯。
方法 3:使用固定位置畫布或 WebGL
最後一個選項是使用頁面背面的固定位置畫布,繪製經過轉換的圖片。乍看之下可能不是最有效的解決方案,但其實這種方法有幾個優點:
- 由於只有一個元素,也就是畫布,我們不再需要太多的合成器工作。
- 我們正在有效處理單一硬體加速點陣圖。
- Canvas2D API 非常適合我們要執行的轉換作業,意味著開發和維護工作更容易管理。
使用畫布元素提供了新的層,但它只是一個圖層;而在選項 2 中,我們為套用 3D 轉換的「所有」元素指派了新圖層,因此我們的工作負載會越來越多,組合這些圖層。因應不同瀏覽器的轉換導入方式,這也是目前最相容的解決方案。
/**
* 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));
}
這個方法在處理大型圖片 (或其他可以輕鬆寫成畫布的元素) 上時都很有用,而且當然可以處理大量文字區塊。不過,視您的網站而定,這或許是最合適的解決方案。如果您「確實」需要處理畫布中的文字,則必須使用 fillText
API 方法,但代價是為無障礙需求 (只要將文字光柵化為點陣圖!),您現在必須處理換行以及處理其他問題的整個堆積。如果可以,您應該真的是這樣,而且使用上述的轉換方法可能會更好。
看到我們盡可能多做一點時,沒有理由假設應在畫布元素內完成視差工作。如果瀏覽器支援 WebGL,我們便會使用 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();
}
這個方法最後的一點是,如果不太喜愛在網頁中加入額外元素,可以在 Firefox 和 WebKit 瀏覽器中使用畫布作為背景元素。這一點顯而易見,因此請謹慎處理。
一切由您決定
開發人員預設採用絕對定位元素 (而非其他選項) 的主要原因,可能只是輔助支援機制。也就是說,還算是誤解,因為原本鎖定的舊版瀏覽器可能會提供極差的轉譯體驗。即使目前的新世代瀏覽器採用絕對位置的元素,也不一定能帶來良好效能!
尤其是 3D 種類的轉換,能讓您直接使用 DOM 元素,進而達到穩固的影格速率。成功的關鍵在於盡可能避免繪製元素,只要嘗試並隨時間移動元素即可。請記住,WebKit 瀏覽器建立圖層的方式,不一定與其他瀏覽器引擎相關,因此請務必先測試再考慮使用該項解決方案。
如果您只著重在頂層瀏覽器,並使用畫布顯示網站,或許是最佳選擇。特別是,如果您正在使用 Three.js,應該就能根據所需的支援,輕鬆在轉譯器間切換及變更。
結論
我們已經評估過幾種處理視差網站的方法,從絕對定位元素到使用固定的位置畫布。至於要做什麼,端看您想達成的目標和採用的具體設計而定,不過建議您多瞭解一些方法。
別忘了,無論您嘗試哪一種方式,都請不猜測,但試一試。