做者:Alexander Skutin , 2014.5.26 . 由Max shirshin與2014年6月30日翻譯(俄語 -> 英語)
現今咱們應更加註重網頁渲染,及其在web開發中的重要性。雖然不少文章都曾談到這一主題,但大可能是分散和割裂的。譬如爲了對這個主題有更全面的認識須要去搜索不少的信息來源,而這也是筆者決定寫這篇文章的緣由。筆者相信本篇文章會有益於初級開發者,固然對但願可以更新和整理已有知識的中高級開發者一樣可以有所裨益。
當頁面佈局定義完成後,頁面渲染的過程與樣式和腳本所承擔的重要角色同樣,也須要在初期就開始進行優化。專業的開發者須要瞭解這些技巧以免性能的問題。
本篇文章並不會詳細介紹瀏覽器的內部工做機制,但將會提供一些通用的規則。這是因爲不一樣的瀏覽器引擎有着不一樣的工做方式,太針對與某一個特定瀏覽器的研究會使這個過程變得過於複雜。
瀏覽器如何渲染網頁?
首先概述瀏覽器渲染網頁的過程:
一、文檔對象模型(Document Object Model = DOM)將首先歷來自於服務器的HTML中生成。
二、加載並解析樣式,造成樣式對象模型(CSS Object Mode = CSSOM)。
三、在DOM和CSSOM上層,建立一個渲染樹,這是由將被渲染的對象造成的集合(webkit內核稱這些對象爲「渲染者」或「渲染對象」,在Gecko內核中則稱爲「幀」)。渲染樹會影響DOM結構,但隱藏元素則不在此列(例如<head>標籤,或具備display:none屬性的元素)。在渲染樹中的每一個文本字符串都被表述爲一個單獨的渲染對象,而每一個渲染對象都將包含其應有的DOM結構(或文本塊)以及計算後的樣式。從另外一個角度上說,這個渲染樹描述了DOM中的可見表徵。
四、計算渲染樹中元素的座標,從而成爲「層」。瀏覽器使用一系列方法(這一系列方法僅須要一個節拍(pass))來放置全部的元素(表格須要多個節拍)。
五、最後,頁面將在瀏覽器窗口中顯示出來,這個過程稱爲「繪製」。
當用戶與頁面交互時,或者腳本改變頁面時,前面說起的這些操做都會重複執行,頁面佈局改變時一樣如此。
重繪
當改變了元素樣式,而這個變更並不會影響元素在頁面中的位置時(例如background-color,border-color,visibility),瀏覽器會依據新的樣式僅重繪這個元素(即重繪和從新應用樣式會發生)
從新佈局
當改變影響了文檔內容、結構、或元素位置時,佈局會從新應用和生成。這些改變一般由如下行爲觸發:
- DOM操做(元素的添加、刪除、修改以及調整順序)
- 內容變動,包括表單域中的文字修改
- CSS屬性的計算和修改
- 樣式表的添加和移除
- class屬性的修改
- 瀏覽器窗口變更(大小、滾動)
- 僞類激活(:hover)
如何優化
瀏覽器渲染
瀏覽器會盡量的將重繪和重排限制在受影響的元素區域內。例如,對一個絕對定位/懸浮定位的元素尺寸大小的變動會僅僅影響這個元素及他的後代,然而一個靜態定位元素的尺寸變化會讓在其以後的全部元素都觸發從新佈局。
另外一個優化的技巧是,當運行javascript代碼時,瀏覽器會緩存這些變動,而後在代碼運行以後,將變動放到一個單獨的節拍中再應用。例如,下面的代碼會僅觸發一次重繪和從新佈局。
var $body = $('body');javascript
$body.css('padding', '1px'); // 重排, 重繪php
$body.css('color', 'red'); // 重繪css
$body.css('margin', '2px'); // 重排,重繪html
// 以上步驟下實際僅發生一次重排和重繪
可是就像上面提到的,從新計算一個元素屬性將會觸發一次強制的從新佈局。當咱們添加額外一行代碼來獲取元素屬性時,將會產生一次強制重排
var $body = $('body');java
$body.css('padding', '1px');jquery
$body.css('color', 'red');
$body.css('margin', '2px');web
最終,咱們將形成兩次重排而並不是只是一次。正因如此,你須要將獲取元素屬性歸到一次,來優化性能。(來看示例)
當你不得不觸發強制重繪時,會有一些情況發生。例如:咱們將要把一條相同的屬性兩次應用到一個相同的元素上。最初,這個元素會以無動畫的方式設爲100px,而後須要帶動畫的調整爲50px。你能夠經過這個示例研究,但我會更詳細的描述這個狀況。
咱們首先建立一個具備transition屬性的css類
.has-transition {瀏覽器
-webkit-transition: margin-left 1s ease-out;緩存
-moz-transition: margin-left 1s ease-out;服務器
-o-transition: margin-left 1s ease-out;
transition: margin-left 1s ease-out;
}
而後進行如下處理:
// 具備has-transition類的元素
var $targetElem = $('#targetElemId');
//移除has-transition類
$targetElem.removeClass('has-transition');
// 在css類再也不存在後,改變這個屬性確認動畫已經去除
$targetElem.css('margin-left', 100);
// 增長has-transition類
$targetElem.addClass('has-transition');
// 修改屬性
$targetElem.css('margin-left', 50);
這個實現並不會像指望的方式工做。這些改變被緩存起來,並會在最後的代碼塊中應用。由於咱們須要一個強制的重排,經過如下方式能夠實現:
//移除has-transition類
$(this).removeClass('has-transition');
//修改屬性
$(this).css('margin-left', 100);
// 觸發強制重排,從而在class和屬性中的改變能夠馬上生效
$(this)[0].offsetHeight;
// 示例,其餘的屬性也能夠實現
// 增長has-transition類
$(this).addClass('has-transition');
// 修改屬性
$(this).css('margin-left', 50);
如今將會按設想中的工做了。
實際開發中的優化建議
經過總結一些有用的信息,(做者)提供如下建議:
- 建立有效的html和css,但不要忘記明確文檔編碼。樣式能夠添加到<head>中,腳本則添加到<body>的尾部。
- 嘗試簡化和優化css選擇器(使用CSS預處理的開發者一般不會注意這種優化方法),儘可能保持低級的css嵌套。這也是css選擇器標識性能的方法(從最快的那個開始)。
-
- 一、標識符 #id
- 二、類 .class
- 三、標籤: div
- 四、兄弟選擇器:a+i
- 五、鄰接父子節點選擇器 ul > li
- 六、通用選擇器:*
- 七、特性選擇器:input[type="text"]
- 八、僞類和僞元素:a:hover。你須要記住,瀏覽器處理CSS選擇器的順序是從右至左,這就是爲何最右邊的選擇器應當是最快的那種,好比#id或.class
div * {...} 壞
.list li {...} 壞
.list-item {...} 好
#list .list-item {...} 好
一、在你的腳本中,儘量的減小對dom的操做,儘可能緩存那些會須要重複用到類型和對象。在執行復雜的操做時,比較好的方法是:操做一個「離線」的元素,並在結束後從新增長到DOM樹中(離線的元素即脫離了DOM後在內存中保存的元素)。
三、爲了修改元素的樣式,修改元素的"class"屬性是一般會採用的方法。執行這種操做的DOM節點的層次越深越好。(同時也是因爲深層次定義有利於邏輯解耦)
四、若是能夠的話,僅對那些絕對定位或懸浮定位的元素執行動畫。
五、滾動時去除複雜的僞元素動畫(例如:hover,能夠給body增長一個額外的沒有hover動畫的樣式)。
若須要更詳細的說明,請參見如下文章:
1. How browsers work
2. Rendering: repaint, reflow/relayout, restyle
但願本文可以有所啓發!
30th June 2014