淺談web特性~

導語:

文中多處引用牛人博客觀點,重在分享知識和自己的一些見解~

一,瀏覽器渲染頁面流程

1.瀏覽器解析html源碼,然后創(chuàng)建一個 DOM樹。
在DOM樹中,每一個HTML標簽都有一個對應的節(jié)點,并且每一個文本也都會有一個對應的文本節(jié)點。DOM樹的根節(jié)點就是 documentElement,對應的是html標簽。

2.瀏覽器解析CSS代碼,計算出最終的樣式數(shù)據(jù)。
對CSS代碼中非法的語法她會直接忽略掉。解析CSS的時候會按照如下順序來定義優(yōu)先級:瀏覽器默認設置 < 用戶設置 < 外鏈樣式 < 內(nèi)聯(lián)樣式 < html中的style。

3.構建出DOM樹,并且計算出樣式數(shù)據(jù)后,下一步就是構建一個 渲染樹(rendering tree)。渲染樹和DOM樹有點像,但是是有區(qū)別的。DOM樹完全和html標簽一一對應,但是渲染樹會忽略掉不需要渲染的元素,比如head、display:none的元素等。而且一大段文本中的每一個行在渲染樹中都是獨立的一個節(jié)點。渲染樹中的每一個節(jié)點都存儲有對應的css屬性。

4.一旦渲染樹創(chuàng)建好了,瀏覽器就可以根據(jù)渲染樹直接把頁面繪制到屏幕上。

重繪和重排(repaints and reflows)

每個頁面至少在初始化的時候會有一次重排操作。任何對渲染樹的修改都有可能會導致下面兩種操作:

1.重排就是渲染樹的一部分必須要更新 并且節(jié)點的尺寸發(fā)生了變化。這就會觸發(fā)重排操作。

2.重繪部分節(jié)點需要更新,但是沒有改變他的集合形狀,比如改變了背景顏色,這就會觸發(fā)重繪。什么情況下會觸發(fā)重繪或重排

下面任何操作都會觸發(fā)重繪或者重排:

  • 增加或刪除DOM節(jié)點設置 display: none;(重排并重繪) 或visibility: hidden(只有重排)
  • 移動頁面中的元素
  • 增加或者修改樣式用戶
  • 改變窗口大小
  • 滾動頁面等

減少重繪和重排

1.不要一個一個地單獨修改屬性,最好通過一個classname來定義這些修改

// badvar left = 10, top = 10;
el.style.left = left + "px";
el.style.top = top + "px";
// better el.className += " theclassname";

2.把對節(jié)點的大量修改操作放在頁面之外用 documentFragment來做修改clone 節(jié)點,在clone之后的節(jié)點中做修改,然后直接替換掉以前的節(jié)點通過 display: none 來隱藏節(jié)點(直接導致一次重排和重繪),做大量的修改,然后顯示節(jié)點(又一次重排和重繪),總共只會有兩次重排。

3.不要頻繁獲取計算后的樣式。如果你需要使用計算后的樣式,最好暫存起來而不是直接從DOM上讀取。

4.總的來說,總是考慮到渲染樹得存在,考慮到你的一次修改會導致多大的繪制操作。比如絕對定位元素的動畫就不會影響其他大部分元素。

有個小段子:

1.用戶輸入網(wǎng)址(假設是個html頁面,并且是第一次訪問),瀏覽器向服務器發(fā)出請求,服務器返回html文件;

2.瀏覽器開始載入html代碼,發(fā)現(xiàn)<head>標簽內(nèi)有一個<link>標簽引用外部CSS文件;

3.瀏覽器又發(fā)出CSS文件的請求,服務器返回這個CSS文件;

4.瀏覽器繼續(xù)載入html中<body>部分的代碼,并且CSS文件已經(jīng)拿到手了,可以開始渲染頁面了;

5.瀏覽器在代碼中發(fā)現(xiàn)一個<img>標簽引用了一張圖片,向服務器發(fā)出請求。此時瀏覽器不會等到圖片下載完,而是繼續(xù)渲染后面的代碼;

6.服務器返回圖片文件,由于圖片占用了一定面積,影響了后面段落的排布,因此瀏覽器需要回過頭來重新渲染這部分代碼;

7.瀏覽器發(fā)現(xiàn)了一個包含一行Javascript代碼的<script>標簽,趕快運行它;

8.Javascript腳本執(zhí)行了這條語句,它命令瀏覽器隱藏掉代碼中的某個<div> (style.display=”none”)。杯具啊,突然就少了這么一個元素,瀏覽器不得不重新渲染這部分代碼;

9.終于等到了</html>的到來,瀏覽器淚流滿面……

10.等等,還沒完,用戶點了一下界面中的“換膚”按鈕,Javascript讓瀏覽器換了一下<link>標簽的CSS路徑;

11.瀏覽器召集了在座的各位<div><span><ul><li>們,“大伙兒收拾收拾行李,咱得重新來過……”,瀏覽器向服務器請求了新的CSS文件,重新渲染頁面。

有個小例子:

以下代碼在Chrome 51 Release版的Timeline


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
  <link rel="stylesheet" href="index.css">
</head>
<body>
</body>
<script>
  document.querySelector('body').innerHTML = 'hello world';
</script>
</html>

頁面的加載過程為:

0.Event(beforeunload)

1.Send Request

2.Receive Response

3.Receive Data

4.Event(pagehide)

5.Event(visibilitychange)

6.Event(webkitvisibilitychange)

7.Event(unload)

8.Event(readystatechange)

9.Finish loading

10.Parse HTML

11.Update Layer Tree

12.Paint

13.firstPaint

14.firstContentfullPaint

15.composite Layers

16.Major GC(0 B collected)

17.Minor GC(123 KB collected)

注:
GC,即垃圾回收。

如果你還不太明白,這個是最后的解釋:

網(wǎng)頁渲染必須在很早的階段進行,可以早到頁面布局剛剛定型。因為樣式和腳本都會對網(wǎng)頁渲染產(chǎn)生關鍵性的影響。所以專業(yè)開發(fā)者必須了解一些技巧,從而避免在實踐的過程中遇到性能問題。

渲染引擎的主要目的就是從一個網(wǎng)頁的URL開始,經(jīng)過一系列的復雜處理過程之后,變成一個可視化的結果,這一過程就是這里所說的頁面渲染的基本過程。
所謂的渲染,就是根據(jù)描述或者定義構建數(shù)學模型,通過模型生成圖像的過程。瀏覽器的渲染引擎就是能夠?qū)TML/CSS/JavaScript轉(zhuǎn)換成圖像結果的模塊,如下圖所示,輸入是URL對應的各種資源,輸出是可視化的圖像。從這里看,非常的簡單和容易理解。

URL對應的各種資源,輸出是可視化的圖像。從這里看,非常的簡單和容易理解。

模型.png

渲染模塊

那么渲染引擎提供了哪些功能模塊來支持頁面渲染的呢?下圖是一個渲染引擎所包含的基本功能和它們依賴的一些第三方庫。

 引擎 
渲染.png

從圖中大致可以看出,一個渲染引擎大致包括HTML解釋器,CSS解釋器,布局和JavaScript引擎。下面依次來描述它們:
HTML解釋器:解釋HTML語言的解釋器,本質(zhì)是將HTML文本解釋成DOM(文檔對象模型)樹。
CSS解釋器:解釋樣式表的解釋器,其作用是將DOM中的各個元素對象加上樣式信息,從而為計算最后結果的布局提供依據(jù)。
布局:DOM之后,需要將其中的元素對象同樣式信息結合起來,計算它們的大小位置等布局信息,形成一個能夠表示這所有信息的內(nèi)部表示模型。
JavaScript引擎:JavaScript可以修改網(wǎng)頁的內(nèi)容,也能修改CSS的信息,JavaScript引擎解釋JavaScript代碼并把代碼的邏輯和對DOM和CSS的改動信息應用到布局中去,從而改變渲染的結果。
這些模塊依賴很多其他的基礎模塊,這其中包括網(wǎng)絡,存儲,2D/3D圖形,音頻視頻和圖片解碼器等。實際上,渲染引擎中還應該包括如何使用這些依賴模塊的部分,這部分的工作其實并不少,因為需要使用它們來高效的渲染網(wǎng)頁。例如,利用2D/3D圖形庫來實現(xiàn)高性能的網(wǎng)頁繪制和網(wǎng)頁的3D渲染,這個實現(xiàn)非常非常的復雜。最后,當然,在最下面,依然少不了操作系統(tǒng)的支持,例如線程支持,文件支持等等。

基本過程

瀏覽器是如何完成網(wǎng)頁渲染?

首先,我們回顧一下網(wǎng)頁渲染時,瀏覽器的動作:

根據(jù)來自服務器端的HTML代碼形成文檔對象模型(DOM)。
加載并解析樣式,形成CSS對象模型。
在文檔對象模型和CSS對象模型之上,創(chuàng)建一棵由一組待生成渲染的對象組成的渲染樹(在Webkit中這些對象被稱為渲染器或渲染對象,而在Gecko中稱之為“frame”。)渲染樹反映了文檔對象模型的結構,但是不包含諸如標簽或含有display:none屬性的不可見元素。在渲染樹中,每一段文本字符串都表現(xiàn)為獨立的渲染器。每一個渲染對象都包含與之對應的DOM對象,或者文本塊,還加上計算過的樣式。換言之,渲染樹是一個文檔對象模型的直觀展示。
對渲染樹上的每個元素,計算它的坐標,稱之為布局。瀏覽器采用一種流方法,布局一個元素只需通過一次,但是表格元素需要通過多次。
最后,渲染樹上的元素最終展示在瀏覽器里,這一過程稱為“painting”。
當用戶與網(wǎng)頁交互,或者腳本程序改動修改網(wǎng)頁時,前文提到的一些操作將會重復執(zhí)行,因為網(wǎng)頁的內(nèi)在結構已經(jīng)發(fā)生了改變。

Repaint

當改變那些不會影響元素在網(wǎng)頁中的位置的元素樣式時,譬如background-color(背景色), border-color(邊框色), visibility(可見性),瀏覽器只會用新的樣式將元素重繪一次(這就是重繪,或者說重新構造樣式)。

Reflow

當改變影響到文本內(nèi)容或結構,或者元素位置時,重排或者說重新布局就會發(fā)生。這些改變通常由以下事件觸發(fā):

DOM操作(元素添加、刪除、修改或者元素順序的改變);
內(nèi)容變化,包括表單域內(nèi)的文本改變;
CSS屬性的計算或改變;
添加或刪除樣式表;
更改“類”的屬性;
瀏覽器窗口的操作(縮放,滾動);
偽類激活(懸停)。
瀏覽器如何優(yōu)化渲染?

瀏覽器盡可能將 repaint/reflow 限制在被改變元素的區(qū)域內(nèi)。比如,對于位置固定或絕對的元素,其大小改變只影響元素本身及其子元素,然而,靜態(tài)定位元素的大小改變會觸發(fā)后續(xù)所有元素的重流。

另一種優(yōu)化技巧是,在運行幾段JavaScript代碼時,瀏覽器會緩存這些改變,在代碼運行完畢后再將這些改變經(jīng)一次通過加以應用。舉個例子,下面這段代碼只會觸發(fā)一個reflow和repaint:

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 

然而,如前所述,改變元素的屬性會觸發(fā)強制性的重排。如果我們在上面的代碼塊中加入一行代碼,用來訪問元素的屬性,就會發(fā)生這種現(xiàn)象。

var body=(‘body’); 
body.css(‘padding′,‘1px′);
body.css(‘padding’); // reading a property, a forced reflow 
body.css(‘color′,‘red′);
body.css(‘margin’, ‘2px’); 

其結果就是,重排發(fā)生了兩次。因此,你應該把訪問元素屬性的操作都組織在一起,從而優(yōu)化網(wǎng)頁性能。

有時,你必須觸發(fā)一個強制性重排。比如,我們必須將同樣的屬性(比如左邊距)兩次賦值給同一個元素。起初,它應該設置為100px,且不帶動效。接著,它必須通過過渡(transition)動效改變?yōu)?0px。你現(xiàn)在可以在JSbin上學習這個例子,不過我會在這兒更詳細地介紹它。

首先,我們創(chuàng)建一個帶過渡效果的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; 
}

然后繼續(xù)執(zhí)行:

 // 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); 

然而,這個執(zhí)行無法奏效。所有改變都被緩存,只在代碼塊末尾加以執(zhí)行。我們需要的是強制性的重排,我們可以通過以下更改加以實現(xiàn):

// 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);

現(xiàn)在代碼如預期那樣執(zhí)行了。

二,性能優(yōu)化

創(chuàng)建有效的HTML和CSS文件,不要忘記指明文檔的編碼方式。樣式應該包含在標簽內(nèi),腳本代碼則應該加在標簽末端。
盡量簡化和優(yōu)化CSS選擇器(這種優(yōu)化方式幾乎被使用CSS預處理器的開發(fā)者統(tǒng)一忽視了)將嵌套程度保持在最低水平。以下是CSS選擇器的性能排名(從最快者開始):

  • 識別器:#id
  • 類:.class
  • 標簽:div
  • 相鄰兄弟選擇器:a + i
  • 父類選擇器:ul> li
  • 通用選擇器:*
  • 屬性選擇:input[type=”text”]
  • 偽類和偽元素:a:hover

你應該記住,瀏覽器在處理選擇器時依照從右到左的原則,因此最右端的選擇器應該是最快的:#id或則.class

  • div * {…} // bad
  • .list li {…} // bad
  • .list-item {…} // good
  • list .list-item {…} // good

在你的腳本代碼中,盡可能減少DOM操作。緩存所有東西,包括元素屬性以及對象(如果它們被重用的話)。當進行復雜的操作時,使用“孤立”元素會更好,之后可以將其加到DOM中(所謂“孤立”元素是與DOM脫離,僅保存在內(nèi)存中的元素)。
如果你使用jQuery來選擇元素,請遵從jQuery選擇器最佳實踐方案。
為了改變元素的樣式,修改“類”的屬性是奏效的方法之一。執(zhí)行這一改變時,處在DOM渲染樹的位置越深越好(這還有助于將邏輯與表象脫離)。
盡量只給位置絕對或者固定的元素添加動畫效果。
在使用滾動時禁用復雜的懸停動效(比如,在中添加一個額外的不懸停類)
了解模塊之后,下面就是這些模塊如何組織以達成渲染過程的。一般地,一個典型的渲染過程下圖所示,這是渲染引擎的核心過程,一切都是圍繞著它來的。

guo
引擎.png

下面逐個從左至右來解釋上圖中的這一過程。這一過程的先后關系由圖中的實線箭頭表示。左上角開始,首先是網(wǎng)頁內(nèi)容,送到HTML解釋器。HTML解釋器在解釋它后形成DOM樹,中間如果遇到JavaScript代碼則交給JavaScript引擎去處理。如果頁面包含CSS,則交給CSS解釋器去解析。當DOM建立的時候,接受來自CSS解釋的樣式信息,構建一個新的內(nèi)部繪圖模型。該模型由布局模塊計算模型內(nèi)部的各個元素的位置和大小信息,最后由繪圖模塊完成從該模型到圖像的繪制。
最后解釋圖中虛線箭頭的指向含義。它們表示在渲染過程中,每個階段可能使用到的其他模塊。在網(wǎng)頁內(nèi)容的下載中,需要使用到網(wǎng)絡和存儲,這個是顯而易見地。但計算布局和繪圖的時候,需要使用2D/3D的圖形模塊,同時因為要生成最后的可視化結果,這時候需要開始解碼音頻視頻和圖片,同其它內(nèi)容一起繪制到最后的圖像中。
在渲染完成之后,用戶可能需要跟渲染的結果進行交互,或者網(wǎng)頁自身有動畫,一般而言,這會持續(xù)的重新渲染過程。這個過程跟上面類似,不再贅述。

在此特別感謝inyiyi的博客,學習很多~inyiyi的博客

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標簽默認的外補...
    _Yfling閱讀 14,131評論 1 92
  • 簡介瀏覽器可以被認為是使用最廣泛的軟件,本文將介紹瀏覽器的工 作原理,我們將看到,從你在地址欄輸入google.c...
    聽風閣閱讀 3,393評論 0 7
  • 轉(zhuǎn)載說明 一、介紹 瀏覽器可以被認為是使用最廣泛的軟件,本文將介紹瀏覽器的工作原理,我們將看到,從你在地址欄輸入g...
    17碎那年閱讀 2,525評論 0 22
  • 1. 介紹 瀏覽器可能是最廣泛使用的軟件。本書將介紹瀏覽器的工作原理。我們將看到,當你在地址欄中輸入google....
    康斌閱讀 2,162評論 7 18
  • 目錄一、介紹二、渲染引擎三、解析與DOM樹構建四、渲染樹構建五、布局六、繪制七、動態(tài)變化八、渲染引擎的線程九、CS...
    饑人谷_米彌輪閱讀 2,511評論 0 10

友情鏈接更多精彩內(nèi)容