https://sylvanassun.github.io/2017/10/03/2017-10-03-BrowserCriticalRenderingPath/
http://www.ruanyifeng.com/blog/2015/09/web-page-performance-in-depth.html
瀏覽器渲染過程,分為以下幾步:
1:構建dom樹
2:構建css樹,
3:合并dom樹以及css樹,構建渲染樹,在這個過程中,會過濾掉不可見節點。例如使用css設置了display:none屬性的節點,但是要注意一點,visibility: hidden這個屬性不是不可見屬性,他的意思是隱藏dom節點,但是仍然占據布局空間
4:生成布局,遍歷每一個節點,計算出dom元素的位置(重排)
5:將節點繪制到屏幕上。(重繪)
如果瀏覽器渲染一個頁面,首先就是要構建dom樹以及css樹,如果頁面非常復雜,這兩步就會給頁面加載速度帶來影響。
dom樹優化
所以對于dom樹的優化,我們在寫頁面時,應該應該盡可能的減少dom元素
css樹優化
對于css樹的優化,可以讓css在不同的情況下加載不同的css資源,而不是一次性全部加載,這樣就可以減少css樹的構建時間。這一步的優化可以使用css的媒體查詢來實現。
媒體查詢是由媒體類型以及0個或多個檢查媒體特定類型的表達式組成。具體如下:
注意:使用媒體查詢可以讓css文件不會再加載時阻塞渲染,但是瀏覽器依然會將文件下載下來的。
優化JavaScript腳本
當html解析器遇到一個script標簽時,會暫停構建dom樹,將控制權交給JavaScript引擎,然后由JavaScript引擎去執行腳本,這會嚴重阻塞dom樹的構建過程。而且在執行JavaScript腳本的過程中,如果操作了css,而這個css文件還沒有下載和執行,那么腳本執行以及dom樹構建都會被阻塞,等待瀏覽器下載css文件以及構建完css樹后,才會接著執行JavaScript腳本。所以JavaScript腳本的位置放的位置非常重要,不然會嚴重阻塞瀏覽器渲染
在執行JavaScript腳本時,如果操作了dom元素,而這個元素還沒有構建好,那么就會報錯。所以就講script標簽放到頁面的最下方,或者使用window.onload()和JQuery中的$(function(){})這兩個方法。不過這兩個方法還是有區別的,onload是在頁面渲染完成以后,執行JavaScript腳本,而jq的方法是在dom樹構建完成后就開始執行了。
給script標簽加上async屬性,可以使這個script腳本異步加載,并不會阻塞dom樹的構建,但當這個腳本異步下載完成后,會直接執行這個腳本
總結:
網頁請求到渲染的整個過程
瀏覽器通過url請求到html頁面,然后html解析器開始構建dom樹,構建的過程中,如果遇到link標簽,那么瀏覽器就會請求這個css文件,然后開始構建css樹,如果遇到script標簽,那么瀏覽器就會請求這個腳本文件,html解析器會將控制器權交給JavaScript引擎,執行這個腳本。當dom樹與css樹都構建出來后,就會合并出渲染樹,接著便是計算位置,繪制到屏幕上。
在這個過程中,
優化css樹的方法使用媒體查詢,根據不同的媒體狀態,加載不同的css資源。
優化JavaScript腳本的方法就是異步,使用async這個屬性
描述關鍵渲染路徑性能的詞匯:
關鍵資源:指的是html頁面、css文件以及JavaScript腳本
關鍵路徑長度:獲取資源的往返次數或者總時間
關鍵字節:所有資源文件的總和
關鍵路徑渲染:瀏覽器接收到HTML文件、css文件、JavaScript腳本并對其進行解析以及轉換成像素的整個渲染過程稱為關鍵路徑渲染
其他的優化方案:
1:加載部分HTML,其他的HTML通過ajax請求返回
2:通過對外部資源進行壓縮來減少關鍵路徑長度以及關鍵字節
在進行文件壓縮之前,可以先進行一次冗余壓縮,比如注釋、空格符換換行符
3:http緩存
可以將一些靜態的資源緩存起來,然后每次請求的時候帶一個標示,后臺通過這個標示進行判斷資源是否有改變,如果改變則返回新的資源文件,如果沒改變,則返回304,復用緩存。
4:資源預加載
資源預加載是推測出用戶接下來有可能訪問哪些資源,然后對這些資源進行預加載
預加載的方式有以下幾種
1:提前進行dns解析,以便之后快速訪問另一個主機名? //?rel="dns-prefetch" href="other.hostname.com"
2:提前下載好資源文件, 使用的屬性不同,優先級不同。prefetch 優先級最低??subresource 優先級最高
3:提前將頁面渲染出來 并隱藏 //?link rel="prerender"
var margin=parseInt(div.style.marginTop)
div.style.marginTop=(margin+10)+'px'
而這段代碼就會使瀏覽器渲染兩次。先做了寫操作,然后對dom元素進行讀操作的時候就會造成dom元素重新排版,也就是重新渲染。然后獲取到值后又進行了寫操作,所以會進行兩次重新渲染。
改成如下:
var margin=parseInt(div.style.marginTop)
div.style.color='blue'
div.style.marginTop=(margin+10)+'px'
這段代碼先進行了讀操作,接著進行了兩次寫操作,所以只會重新渲染一次。
其他提高性能的方法:
1:如果某個樣式是重排得到的,建議進行緩存,這樣便不用每次獲取值都造成重新渲染
2:對dom如果要進行多次讀寫操作,建議使用虛擬dom。
方法:將一個dom元素進行clone,然后操作完畢后可以插入或替換原節點。
如果進行多次讀寫操作,有可能造成多次重新渲染,而使用虛擬dom的方式只會在替換或者插入時進行一次重排
3:absolute或fixed的元素重繪的開銷比較小,因為不用考慮對別的dom元素的影響
4:display設置為可見時,會進行重繪和重排,因為display為none時,并不占據空間。visibility 從hidden變為可見時,只進行重繪,并不會進行重排。因為visibility 的值為隱藏時,這個dom元素依然占據空間
5:使用 window.requestAnimationFrame()、window.requestIdleCallback() 這兩個方法調節重新渲染
瀏覽器渲染與優化整個流程總結:
網頁請求到渲染整個流程:
瀏覽器通過url請求到html頁面,然后html解析器開始構建dom樹,構建的過程中,如果遇到link標簽,那么瀏覽器就會請求這個css文件,然后開始構建css樹,如果遇到script標簽,那么瀏覽器就會請求這個腳本文件,html解析器會將控制器權交給JavaScript引擎,執行這個腳本。當dom樹與css樹都構建出來后,就會合并出渲染樹,接著便是計算位置,繪制到屏幕上。
整個渲染流程分為五步:
1:構建css樹
優化方式:使用媒體查詢,將不同的css拆開。讓css在不同的情況下,加載不同的css資源。比如:media=print,可以使打印的css資源在用戶瀏覽器的過程中不會阻塞渲染
2:構建dom樹
優化方式:布局時盡可能少的dom元素寫成所需的UI效果
3:dom樹與css樹合并,構建渲染樹
4:根據渲染樹生成布局(如果用戶在瀏覽過程中,改變了dom元素,就會重新布局,也就是重排)
5:布局生成后,就會將dom節點繪制到屏幕上(重新布局后,就是重繪)
第四部和第五步的優化:
1:對dom元素的讀寫分開操作,因為讀取某個dom元素的狀態時,并不會造成重繪或者重排,但是如果先使用了寫操作,改變了某個dom的屬性,那么這個時候在進行讀操作時一定會進行重排。
2:對dom節點進行多步操作時,使用虛擬的dom節點。然后在全部操作完成后,將dom節點插入或替換到dom文檔中。因為使用虛擬dom節點,只有在插入或者替換的時候,才會造成瀏覽器重排或者重繪。
3:absolute或fixed的元素重排時開銷比較小,因為這種布局方式不用考慮對文檔流的影響
對JavaScript的優化:首次加載頁面時,JavaScript腳本的執行會影響渲染樹的構建,所以可以給不依賴dom節點的JavaScript腳本加上async屬性。
其他的優化方式:
關鍵資源壓縮、http緩存