瀏覽器 渲染,繪製流程及性能優化

渲染性能

developers.google.com/w

像素渲染流水線

在編寫web頁面時,你需要理解你所寫的頁面代碼是如何被轉換成屏幕上顯示的像素的。

這個轉換過程可以歸納為這樣的一個流水線,包含五個關鍵步驟

JavaScript(DOM) > Style > Layout > Paint > Composite

「布局」 === 重排」

瀏覽器執行的所有步驟:

  1. 處理 HTML 標記,構建 DOM 樹
  2. 處理 CSS 標記,構建 CSSOM 樹
  3. 將 DOM 樹和 CSSOM 樹融合成渲染樹
  4. 根據渲染樹來布局(重排),計算每個節點的幾何信息。
  5. 在屏幕上繪製(重繪)各個節點。

JS DOM > 樣式 CSSOM > 布局 > 繪製 > 合成

JavaScript。

一般來說,我們會使用JavaScript來實現一些視覺變化的效果。比如用jQuery的animate函數做一個動畫、對一個數據集進行排序、或者往頁面里添加一些DOM元素等。當然,除了JavaScript,還有其他一些常用方法也可以實現視覺變化效果,比如:CSS Animations, Transitions和Web Animation API。

計算樣式。這個過程是根據CSS選擇器,比如.headline或.nav > .nav_item,對每個DOM元素匹配對應的CSS樣式。這一步結束之後,就確定了每個DOM元素上該應用什麼CSS樣式規則。

布局

上一步確定了每個DOM元素的樣式規則,這一步就是具體計算每個DOM元素最終在屏幕上顯示的大小和位置。web頁面中元素的布局是相對的,因此一個元素的布局發生變化,會聯動地引發其他元素的布局發生變化。比如,<body>元素的寬度的變化會影響其子元素的寬度,其子元素寬度的變化也會繼續對其孫子元素產生影響。因此對於瀏覽器來說,布局過程是經常發生的。

繪製

繪製,本質上就是填充像素的過程。包括繪製文字、顏色、圖像、邊框和陰影等,也就是一個DOM元素所有的可視效果。一般來說,這個繪製過程是在多個層上完成的。

渲染層合併

由上一步可知,對頁面中DOM元素的繪製是在多個層上進行的。在每個層上完成繪製過程之後,瀏覽器會將所有層按照合理的順序合併成一個圖層,然後顯示在屏幕上。對於有位置重疊的元素的頁面,這個過程尤其重要,因為一旦圖層的合併順序出錯,將會導致元素顯示異常。

上述過程的每一步中都有發生jank的可能,因此一定要弄清楚你的代碼將會運行在哪一步。

rasterize 光柵化 & 光柵化 rasterization

Note: 你可能聽說過 "rasterize" 這個術語,它通常被用在繪製過程中。n繪製過程本身包含兩步: :1)創建一系列draw調用n;2)填充像素。 n第二步的過程被稱作 "rasterization" 。n因此當你在DevTools中查看頁面的paint記錄時,你可以認為它已經包含了 rasterization。(有些瀏覽器會使用不同的線程來完成這兩步,不過這也不是web開發者能控制的了)n

雖然在理論上,頁面的每一幀都是經過上述的流水線處理之後渲染出來的,但並不意味著頁面每一幀的渲染都需要經過上述五個步驟的處理。

實際上,對視覺變化效果的一個幀的渲染,有這麼三種 常用的 流水線:

1. JS / CSS > 計算樣式 > 布局 > 繪製 > 渲染層合併

2. JS / CSS > 計算樣式 > 繪製 > 渲染層合併

3. JS / CSS > 計算樣式 > 渲染層合併

如果你修改一個非樣式且非繪製的CSS屬性,那麼瀏覽器會在完成樣式計算之後,跳過布局和繪製的過程,直接做渲染層合併。

第三種方式在性能上是最理想的,對於動畫和滾動這種負荷很重的渲染,我們要爭取使用第三種渲染流程。

Note: 如果你想對哪些屬性會觸發CSS Triggers和高性能動畫方面了解更多,請參考:使用渲染層合併屬性。

CSS Triggers

CSS Triggers

transformClose

Changing transform does not trigger any geometry changes or painting, which is very good. This means that the operation can likely be carried out by the compositor thread with the help of the GPU.

更改變換不會觸發任何幾何更改或繪畫,這是非常好的。這意味著操作可能由合成器線程在GPU的幫助下執行。

CSS Triggers

Changing float alters the geometry of the element. That means that it may affect the position or size of other elements on the page, both of which require the browser to perform layout operations.

Once those layout operations have completed any damaged pixels will need to be painted and the page must then be composited together.

更改浮動將更改元素的幾何。這意味著它可能會影響頁面上其他元素的位置或大小,這兩個元素都需要瀏覽器執行布局操作。

一旦這些布局操作完成,任何損壞的像素將需要被繪製,然後該頁必須被合成在一起。

developers.google.com/w

性能優化是一門做減法的藝術。

我們首要要儘力簡化頁面渲染過程,然後要使渲染過程的每一步都盡量高效。在很多時候,我們需要跟瀏覽器一起努力來創建高性能web應用,而不是跟瀏覽器對著干。要記住,以上列舉的流水線中的每一步,在時間消耗上是各不相同的,有些步驟是相對更費時的。

接下來,讓我們深入到這個流水線中的每一步去看看。我們會以一些常見問題為例,闡述如何發現和分析這些問題,並嘗試去解決它們。

瀏覽器渲染優化

想深入了解渲染性能嗎?快看看這堂課程吧 它能幫助你了解瀏覽器是如何把HTML/CSS/JavaScript代碼轉換成屏幕上你看到的一個個像素的 如何使用DevTools來測量頁面性能、以及如何優化你的頁面渲染速度。

瀏覽器渲染優化

重新渲染,就需要重新生成布局和重新繪製。前者叫做"重排"(reflow),後者叫做"重繪"(repaint)。

重新渲染,就需要重新生成布局和重新繪製。前者叫做"重排"(reflow),後者叫做"重繪"(repaint)。

需要注意的是,"重繪"不一定需要"重排",比如改變某個網頁元素的顏色,就只會觸發"重繪",不會觸發"重排",因為布局沒有改變。但是,"重排"必然導致"重繪",比如改變一個網頁元素的位置,就會同時觸發"重排"和"重繪",因為布局改變了。

網頁性能管理詳解 - 阮一峰的網路日誌

瀏覽器的重繪(repaints)與重排(reflows)

在項目的交互或視覺評審中,前端同學常常會對一些交互效果質疑,提出這樣做不好那樣做不好。主要原因是這些效果通常會產生一系列的瀏覽器重繪和重排,需要 付出高昂的性能代價。

那麼,什麼是瀏覽器的重繪和重排呢?

二者何時發生以及如何權衡?

如何在具體的開發過程中將重繪和重排引發的性能問題考慮進去?

本文期 待可以部分解釋以上三個問題。

瀏覽器從下載文檔顯示頁面的過程是個複雜的過程,這裡包含了重繪和重排。

各家瀏覽器引擎的工作原理略有差 別,但也有一定規則。

簡單講,通常在文檔初次載入時,瀏覽器引擎會解析HTML文檔來構建DOM樹,之後根據DOM元素的幾何屬性 CSSOM 樹構建一棵渲染樹

渲染樹的每個節點都有大小和邊距等屬性,類似於盒子模型(由於隱藏元素不需要顯示,渲染樹中並不包含DOM樹中隱藏的元素)。

當渲染樹構建完成後,瀏覽器 就可以將元素放置到正確的位置了(Layout 布局),再根據渲染樹節點的樣式屬性繪製出頁面。

由於瀏覽器的流布局,對渲染樹的計算通常只需要遍歷一次就可以完成。

但 table及其內部元素除外,它可能需要多次計算才能確定好其在渲染樹中節點的屬性,通常要花3倍於同等元素的時間。這也是為什麼我們要避免使用 table做布局的一個原因。

重繪是一個元素外觀改變所觸發的瀏覽器行為,例如改變vidibility、outline、背景色等屬性。瀏覽器會根據元素的新屬性重新繪製,使元素呈現新的外觀。

重繪不會帶來重新布局,並不一定伴隨重排。???

重排是更明顯的一種改變,可以理解為渲染樹需要重新計算。下面是常見的觸發重排的操作:

1. DOM元素的幾何屬性變化

當DOM元素的幾何屬性變化時,渲染樹中的相關節點就會失效,瀏覽器會根據DOM元素的變化重建構建渲染樹中失效的節點。

之後,會根據新的渲染樹重新繪 制這部分頁面。而且,當前元素的重排也許會帶來相關元素的重排。

例如,容器節點的渲染樹改變時,會觸發子節點的重新計算,也會觸發其後續兄弟節點的重排, 祖先節點需要重新計運算元節點的尺寸也會產生重排。最後,每個元素都將發生重繪。

可見,重排一定會引起瀏覽器的重繪,一個元素的重排通常會帶來一系列的反 應,甚至觸發整個文檔的重排和重繪,性能代價是高昂的。

2.DOM樹的結構變化

DOM樹的 結構變化時,例如節點的增減、移動等,也會觸發重排。

瀏覽器引擎布局的過程,類似於樹的前序遍歷,是一個從上到下從左到右的過程。通常在這個過程中,當前 元素不會再影響其前面已經遍歷過的元素。

所以,如果在body最前插(::before)入一個元素,會導致整個文檔的重新渲染,而在其後插(::after 偽元素)入一個元素,則不會影響到前面的 元素

3.獲取某些屬性

瀏覽器引擎可能會針對重排做了優化。

比如Opera,它會等到有足夠 數量的變化發生,或者等到一定的時間,或者等一個線程結束,再一起處理,這樣就只發生一次重排。

但除了渲染樹的直接變化,當獲取一些屬性時,瀏覽器為取得 正確的值也會觸發重排。這樣就使得瀏覽器的優化失效了。

這些屬性包括:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、 clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle() (currentStyle in IE)。

所以,在多次使用這些值時應進行緩存

此外,改變元素的一些樣式,調整瀏覽器窗口大小等等也都將觸發重排。

開發中,比較好的實踐是盡量減少重繪次數縮小重排的影響範圍。例如:

1. 將多次改變樣式屬性的操作合併成一次操作。例如,

DOM style 操作 to CSS class 操作

JS:

var changeDiv = document.getElementById(changeDiv); nchangeDiv.style.color = #093; nchangeDiv.style.background = #eee; nchangeDiv.style.height = 200px;n

可以合併為:

CSS:

div.changeDiv {nbackground: #eee;ncolor: #093;nheight: 200px;n}n

JS:

document.getElementById(changeDiv).className = changeDiv;n

2. 將需要多次重排(布局)的元素,position屬性設為absolute或fixed,這樣此元素就脫離了文檔流,它的變化不會影響到其他元素。例如有動畫效果的元素就最好設置為絕對定位

3. 在內存中多次操作節點,完成後再添加到文檔中去。

虛擬DOM) React

例如要非同步獲取表格數據,渲染到頁面。

可以先取得數據後在內存中構建整個表格的變數html片段緩存,再一次性添加到文檔中去,而不是循環添加每一行。

內存中循環

4. 由於display屬性為none的元素不在渲染樹中,對隱藏的元素操作不會引發其他元素的重排。

如果要對一個元素進行複雜的操作時,可以先隱藏它,操作完成後再顯示。這樣只在隱藏和顯示時觸發2次重排。

5. 在需要經常取那些引起瀏覽器重排的屬性值時,要緩存到變數。

在最近幾次面試中比較常問的一個問題:在前端如何實現一個表格的排序

如果應聘者的方案中考慮到了如何減少重繪和重排的影響,將是使人滿意的方案。

推薦閱讀:

Angular AOT優化構建嘗試
如何不擇手段提升scroll事件的性能
LsLoader 移動WEB工程化緩存方案
前端性能優化:客戶端從輸入到展示講解

TAG:GoogleChrome | 前端性能优化 | 渲染 |