2014年5月26日, 由Alexander Skutin撰寫; 由Max Shirshin於2014年6月30日翻譯css
今天我想關注網頁呈現的主題,以及爲何它在網頁開發中很重要。有不少文章可用於涵蓋這個主題,可是信息是分散的,以某種方式分散。例如,爲了包圍個人頭腦,我不得不學習不少來源。這就是爲何我決定寫這篇文章。我相信這篇文章對於初學者以及想要刷新和構建他們已經知道的更多高級開發人員將是有用的。前端
當頁面佈局被定義時,渲染必須從一開始就進行優化,由於樣式和腳本在頁面呈現中起關鍵做用。專業人士必須瞭解某些技巧以免性能問題。web
本文不詳細研究內部瀏覽器的機制,而是提供一些常見的原則。不一樣的瀏覽器引擎的工做方式不一樣,這將使瀏覽器特定的研究變得更加複雜。瀏覽器
瀏覽器如何呈現網頁
咱們從繪製頁面時瀏覽瀏覽器操做的概述開始:緩存
1.DOM(文檔對象模型)由從服務器接收的HTML造成。
2.樣式被加載和解析,造成了CSSOM(CSS對象模型)。
3.在DOM和CSSOM之上,建立一個渲染樹,它是一組要渲染的對象(Webkit調用那些「渲染器」或「渲染對象」,而在Gecko中它是一個「框架」)。渲染樹反映除了不可見元素以外的DOM結構(如<head>標籤或已display:none;設置的元素)。每一個文本字符串在渲染樹中做爲單獨的渲染器表示。每一個渲染對象都包含其對應的DOM對象(或文本塊)加上計算的樣式。換句話說,渲染樹描述了DOM的視覺表示。
對於每一個渲染樹元素,計算其座標,稱爲「佈局」。瀏覽器使用一個流程方法,只須要一次傳遞來佈局全部元素(表須要多個遍)。
最後,這實際上顯示在瀏覽器窗口中,一個稱爲「繪畫」的過程。
當用戶與頁面進行交互或腳本進行修改時,必須重複上述某些操做,由於底層頁面結構發生變化。服務器
重印
當改變不影響頁面上的元素的位置元素的樣式(例如background-color,border-color,visibility),瀏覽器只是應用了新樣式再次重繪元素(即意味着「重畫」或「restyle」正在發生的事情)。app
迴流
當更改影響文檔內容或結構或元素位置時,會發生迴流(或從新傳輸)。這些更改一般是由如下機制觸發的:框架
DOM操做(元素添加,刪除,更改或更改元素順序);
內容更改,包括表單字段中的文本更改;
1計算或改變CSS屬性;
2添加或刪除樣式表;
3改變「類」屬性;
4瀏覽器窗口操縱(調整大小,滾動);
5僞類激活(:hover)。
瀏覽器如何優化渲染
瀏覽器正在盡最大努力將重繪/迴流限制到僅覆蓋已更改元素的區域。例如,絕對/固定定位元素中的大小變化僅影響元素自己及其後代,而靜態定位元素中的類似變化會觸發全部後續元素的迴流。佈局
另外一種優化技術是在運行JavaScript代碼時,瀏覽器會緩存這些更改,並在代碼運行後將其應用於單次傳遞。例如,這段代碼只會觸發一個迴流和重繪:性能
var $body = $('body');
$body.css('padding', '1px'); // reflow, repaint
$body.css('color', 'red'); // repaint
$body.css('margin', '2px'); // reflow, repaint
// only 1 reflow and repaint will actually happen
可是,如上所述,訪問元素屬性會觸發強制迴流。若是咱們添加一個額外的行,將元素屬性讀入上一個塊,則會發生這種狀況:
var $body = $('body');
$body.css('padding', '1px');
$body.css('padding'); // reading a property, a forced reflow
$body.css('color', 'red');
$body.css('margin', '2px');
所以,咱們獲得2個迴流而不是一個迴流。所以,您應該將讀取元素屬性組合在一塊兒以優化性能(請參閱 JSBin上的更詳細示例)。
有時您必須觸發強制迴流。示例:咱們必須將相同的屬性(例如「margin-left」)應用於同一個元素兩次。最初,它應該被設置爲100px沒有動畫,而後它必須是動畫與transition一個值50px。您如今能夠在JSBin上學習這個例子,可是我將在這裏進行更詳細的描述。
咱們首先建立一個具備轉換的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;
}
而後繼續執行:
// our element that has a "has-transition" class by default
var $targetElem = $('#targetElemId');
// remove the transition class
$targetElem.removeClass('has-transition');
// change the property expecting the transition to be off, as the class is not there
// anymore
$targetElem.css('margin-left', 100);
// put the transition class back
$targetElem.addClass('has-transition');
// change the property
$targetElem.css('margin-left', 50);
然而,這種實現不能像預期的那樣工做。這些更改被緩存並應用於代碼塊的末尾。咱們須要的是強制迴流,咱們能夠經過進行如下更改來實現:
// remove the transition class
$(this).removeClass('has-transition');
// change the property
$(this).css('margin-left', 100);
// trigger a forced reflow, so that changes in a class/property get applied immediately
$(this)[0].offsetHeight; // an example, other properties would work, too
// put the transition class back
$(this).addClass('has-transition');
// change the property
$(this).css('margin-left', 50);
如今這樣按預期工做。
優化實用建議
總結可用信息,我能夠推薦如下內容:
建立有效的HTML和CSS,不要忘記指定文檔編碼。樣式應包含在<head>中,附加到<body>標籤末尾的腳本。
嘗試簡化和優化CSS選擇器(這種優化幾乎被大多數使用CSS預處理器的開發人員廣泛忽略)。保持嵌套水平至少。這是CSS選擇器根據其性能(從最快的)開始排名的方式:
識別者: #id
類: .class
標籤: div
相鄰的兄弟選擇器: a + i
家長選擇器 ul > li
通用選擇器 *
屬性選擇器 input[type="text"]
僞類和pseudoelements:a:hover 你應該記住,瀏覽器從右到左的處理選擇,這就是爲何最右邊的選擇應該是最快的國家之一-要麼#id或.class:
div * {...} // bad
.list li {...} // bad
.list-item {...} // good
在腳本中,儘可能減小DOM操做。緩存全部內容,包括屬性和對象(若是要重複使用)。執行復雜操做時,最好使用「離線」元素(「離線」元素是從DOM斷開並僅存儲在內存中),而後將其附加到DOM。
若是您使用jQuery選擇元素,請遵循jQuery選擇器最佳作法。
要更改元素的樣式,修改「類」屬性是最有效的方式之一。您執行此更改的DOM樹越深,越好(也是由於這有助於將邏輯與演示分離)。
動畫只有絕對/固定的元素,若是能夠的話。
在:hover滾動時禁用複雜動畫是一個好主意(例如,經過向<body>添加一個額外的「no-hover」類)。閱讀有關此主題的文章。
有關更詳細的概述,請查看這些文章:
瀏覽器的工做原理
渲染:重繪,迴流/從新傳輸,修復
我但願你能發現這篇文章有用!
2014年6月30日
[RU] РендерингWEB-страницы:чтообэтомдолжензнать前端разработчик
亞歷山大Skutin
現場: http://skutin.ru/Max ShirshinGitHub: ingdir推特: @ingdirFacebook 推特 Google+若是您發現有錯誤,請隨時在GitHub上進行 編輯。評論由Disqus提供支持