1.瀏覽器渲染原理解析
想要提高網頁的性能,首要的便是要理解瀏覽器渲染原理,下面關於瀏覽器的原理解析,我們以chrome內核webkit為例,其他內核的瀏覽器原理也基本大同小異,可觸類旁通。

如上圖所示,瀏覽器解析頁面步驟可分為:
* 解析HTML(HTML Parser)
* 構建DOM樹(DOM Tree)
* 構建CSSOM樹(Style)
* 構建渲染樹(Render Tree)
* 頁面布局(Layout)
* 繪制渲染樹(Painting)
這一過程可在chrome開發者工具的時間線中觀察:

這里我們簡要說一下以下四個概念:
* 布局(layout)
布局也稱為重排或回流,布局流程輸出的是一個“盒模型”,它會精確地捕獲每個元素在視口內的精確位置和尺寸,HTML就是采用基於流的布局模型,頁面元素的變動往往可能導致回流的發生,而回流的頻發發生亦是影響頁面性能的重要因素,另外,處於流后置位通常不會影響前置位的幾何特征,故對后置位的修改往往比對前置位的修改對頁面整體的影響要低。
* 繪制(paint)
繪制即是對DOM所分割的層(layer)進行對應的繪制,頁面的回流一般都會伴隨着重繪,但重繪行為的出現不一定伴隨回流。
* 渲染層
層(layer)的概念對於有設計基礎的人來說應該不陌生,我們平面直觀所見到的圖像是基於空間圖層的重疊得到的,一般來說,擁有相同坐標空間的節點屬於同一個渲染層。渲染層最初是用來實現層疊上下文,以此來保證頁面元素以正確的順序合成(composite),實現半透明重疊等效果。
創建渲染層的條件:
* 根元素(HTML)
* 有明確的position屬性(relative,fixed,sticky,absolute)
* 透明的(opacity小於1)
* 有css濾鏡(filter)
* 有css mask 屬性
* 當前有對於 opacity,transform,fliter,backdrop-filter 應用動畫
* overflow屬性不為visible
* 等等......
* 合成層
合成層是特殊的渲染層,每個合成層有單獨的繪圖層,繪圖層中的繪圖上下文負責輸出該層的位圖,位圖儲存在共享內存中,作為紋理上傳到GPU,最后由GPU將多個位圖進行合成,最后繪制到屏幕上,而相對於合成層,一般的渲染層是和其第一個擁有繪圖層的父層共用一個的繪圖層的,提升為合成層后當需要repaint或reflow本身,不影響其它層,另外,合成層的位圖會直接交由GPU合成處理,效率比CPU高。
渲染層提升為合成層的觸發原因:
* 直接原因
* iframe video canvas flash 元素 有 3D transform
* backface-visibility 為 hidden
* 對 opacity、transform、fliter、backdropfilter 應用了 animation 或 transition
* will-change(設置為 opacity、transform、top、left、bottom、right(其中 top、left 等需要設置明確的定位屬性,如 relative 等))
* 后代原因
* 有合成層后代同時本身有 transform、opactiy(小於 1)、mask、fliter、reflection 屬性
* 有合成層后代同時本身 overflow 不為 visible
* 有合成層后代同時本身 fixed 定位
* 有 3D transform 的合成層后代同時本身有 preserves-3d 屬性
* 有 3D transform 的合成層后代同時本身有 perspective 屬性
* 重疊原因
* 元素的 border box(content + padding + border) 和合成層的有重疊,margin 的重疊無效
* 動畫運行期間,元素可能和其他元素有重疊
2.影響頁面性能的操作及優化分析
* 頻繁操作DOM元素
使用js腳本頻繁地操作DOM元素是影響頁面性能的一大因素,頻繁地對DOM進行操作可能導致頁面重繪和回流的頻繁發生,從而導致頁面卡頓和性能消耗問題,從細節上可按如下方法進行優化:
1)使用文檔片段
var fragment = document.createDocumentFragment(); //一些基於fragment的大量DOM操作 ...... document.getElementById('myElement').appendChild(fragment);
2)設置DOM元素的display樣式為none再操作該元素
var myElement = document.getElementById('myElement'); myElement.style.display = 'none'; //一些基於myElement的大量DOM操作 ...... myElement.style.display = 'block';
3)復制DOM元素到內存中再對其進行操作
var old = document.getElementById('myElement'); var clone = old.cloneNode(true); //一些基於clone的大量操作 ...... old.parentNode.replaceChild(clone, old);
4)用局部變量緩存樣式信息從而避免頻繁獲取DOM數據
//bad operation for (var i = 0; i < paragraphs.length; i++){ paragraphs[i].style.width = box.offsetWidth + 'px'; } //better operation var width = box.offsetWidth; for (var i = 0; i < paragraphs.length; i++){ paragraphs[i].style.width = width + 'px'; }
5)合並多次DOM操作
//bad operation var left = 10, top = 10; el.style.top = top; el.style.left = left; //better operation el.style.cssText += "; left: " + left + "px; top: " + top + "px;"; //better operation(將樣式內容設置於某一類名,再進行元素類名綁定) el.className += " theclassName";
*css動畫造成頁面不流暢問題分析優化
使用css3動畫造成頁面的不流暢和卡頓問題,其潛在原因往往還是頁面的回流和重繪,減少頁面動畫元素對其他元素的影響是提高性能的根本方向,而實現可如下:
1)設置動畫元素position樣式為absolute或fixed,可避免動畫的進行對頁面其它元素造成影響,導致其重排和重繪的發生;
2)避免使用margin,top,left,width,height等屬性執行動畫,用transform進行替代;
//bad operation div { height: 100px; transition: height 1s linear; } div:hover { height: 200px; } //better operation div { transform: scale(0.5); transition: transform 1s linear; } div:hover { transform: scale(1.0); }
總而言之,盡量用transform和opacity完成動畫的展示,因為這兩個屬性可以避免重排和重繪的發生。
頁面渲染的流水線其實可簡單表示為以下步驟,從性能方面考慮,應該盡量避開layout和paint兩個步驟,只觸發composite步驟,但目前能做到這一效果的只有transform和opacity兩個屬性,另外需要注意的是:只有元素提升為合成層的時候transform和opacity才不會觸發paint,否則依舊觸發。

3)合理的提升合成層,以減少頁面不必要的繪制和重排
合成層的好處是不會影響到其他元素的繪制和不被其他層所影響,因此,為了彼此之前的影響造成的性能損失,我們需合理的將動畫效果中的元素或固定元素提升為合成層。
提升合成層的最好方式是使用 CSS 的 will-change 屬性。將will-change 設置為 opacity、transform、top、left、bottom、right 可以將元素提升為合成層。
#target { will-change: transform; }
will-target的兼容性如下:

對於還不兼容該屬性的瀏覽器,我們使用3D transform予以代替
#target { transform: translateZ(0); }
對於像頁面頂部欄,側欄等固定不變的位置元素,我們也可將其提升為合成層以避免其被其他元素影響而發生重繪,但要注意,合成層的提升也意味着性能的消耗增加,我們必須通過調試以測出合理的臨界值,不能盲目提升合成層,此外,盲目提升合成層也可能造成重疊產生的額外合成層,容易導致層爆炸的出現,即頁面連鎖出現大量合成層默認提升,建議用google的timeline進行監控調試,避免出現不必要的意外消耗。
