摘要:對于復雜動畫效果使用絕對定位讓其脫離文檔流對于復雜動畫效果,由于會經常的引起回流重繪,因此,我們可以使用絕對定位,讓它脫離文檔流。硬件加速加速比起考慮如何減少回流重繪,我們更期望的是,根本不要回流重繪。
回流和重繪可以說是每一個web開發者都經常聽到的兩個詞語,我也不例外,可是我之前一直不是很清楚這兩步具體做了什么事情。最近由于部門內部要做分享,所以對其進行了一些研究,看了一些博客和書籍,整理了一些內容并且結合一些例子,寫了這篇文章,希望可以幫助到大家。
瀏覽器的渲染過程本文先從瀏覽器的渲染過程來從頭到尾的講解一下回流重繪,如果大家想直接看如何減少回流和重繪,可以跳到后面。(這個渲染過程來自MDN)
從上面這個圖上,我們可以看到,瀏覽器渲染過程如下:
解析HTML,生成DOM樹,解析CSS,生成CSSOM樹
將DOM樹和CSSOM樹結合,生成渲染樹(Render Tree)
Layout(回流):根據生成的渲染樹,進行回流(Layout),得到節點的幾何信息(位置,大小)
Painting(重繪):根據渲染樹以及回流得到的幾何信息,得到節點的絕對像素
Display:將像素發送給GPU,展示在頁面上。(這一步其實還有很多內容,比如會在GPU將多個合成層合并為同一個層,并展示在頁面中。而css3硬件加速的原理則是新建合成層,這里我們不展開,之后有機會會寫一篇博客)
渲染過程看起來很簡單,讓我們來具體了解下每一步具體做了什么。
生成渲染樹為了構建渲染樹,瀏覽器主要完成了以下工作:
從DOM樹的根節點開始遍歷每個可見節點。
對于每個可見的節點,找到CSSOM樹中對應的規則,并應用它們。
根據每個可見節點以及其對應的樣式,組合生成渲染樹。
第一步中,既然說到了要遍歷可見的節點,那么我們得先知道,什么節點是不可見的。不可見的節點包括:
一些不會渲染輸出的節點,比如script、meta、link等。
一些通過css進行隱藏的節點。比如display:none。注意,利用visibility和opacity隱藏的節點,還是會顯示在渲染樹上的。只有display:none的節點才不會顯示在渲染樹上。
注意:渲染樹只包含可見的節點
回流前面我們通過構造渲染樹,我們將可見DOM節點以及它對應的樣式結合起來,可是我們還需要計算它們在設備視口(viewport)內的確切位置和大小,這個計算的階段就是回流。
為了弄清每個對象在網站上的確切大小和位置,瀏覽器從渲染樹的根節點開始遍歷,我們可以以下面這個實例來表示:
Critial Path: Hello world! Hello world!
我們可以看到,第一個div將節點的顯示尺寸設置為視口寬度的50%,第二個div將其尺寸設置為父節點的50%。而在回流這個階段,我們就需要根據視口具體的寬度,將其轉為實際的像素值。(如下圖)
重繪最終,我們通過構造渲染樹和回流階段,我們知道了哪些節點是可見的,以及可見節點的樣式和具體的幾何信息(位置、大小),那么我們就可以將渲染樹的每個節點都轉換為屏幕上的實際像素,這個階段就叫做重繪節點。
既然知道了瀏覽器的渲染過程后,我們就來探討下,何時會發生回流重繪。
何時發生回流重繪我們前面知道了,回流這一階段主要是計算節點的位置和幾何信息,那么當頁面布局和幾何信息發生變化的時候,就需要回流。比如以下情況:
添加或刪除可見的DOM元素
元素的位置發生變化
元素的尺寸發生變化(包括外邊距、內邊框、邊框大小、高度和寬度等)
內容發生變化,比如文本變化或圖片被另一個不同尺寸的圖片所替代。
頁面一開始渲染的時候(這肯定避免不了)
瀏覽器的窗口尺寸變化(因為回流是根據視口的大小來計算元素的位置和大小的)
注意:回流一定會觸發重繪,而重繪不一定會回流
根據改變的范圍和程度,渲染樹中或大或小的部分需要重新計算,有些改變會觸發整個頁面的重排,比如,滾動條出現的時候或者修改了根節點。
瀏覽器的優化機制現代的瀏覽器都是很聰明的,由于每次重排都會造成額外的計算消耗,因此大多數瀏覽器都會通過隊列化修改并批量執行來優化重排過程。瀏覽器會將修改操作放入到隊列里,直到過了一段時間或者操作達到了一個閾值,才清空隊列。但是!當你獲取布局信息的操作的時候,會強制隊列刷新,比如當你訪問以下屬性或者使用以下方法:
offsetTop、offsetLeft、offsetWidth、offsetHeight
scrollTop、scrollLeft、scrollWidth、scrollHeight
clientTop、clientLeft、clientWidth、clientHeight
getComputedStyle()
getBoundingClientRect
具體可以訪問這個網站:https://gist.github.com/pauli...
以上屬性和方法都需要返回最新的布局信息,因此瀏覽器不得不清空隊列,觸發回流重繪來返回正確的值。因此,我們在修改樣式的時候,最好避免使用上面列出的屬性,他們都會刷新渲染隊列。如果要使用它們,最好將值緩存起來。
減少回流和重繪好了,到了我們今天的重頭戲,前面說了這么多背景和理論知識,接下來讓我們談談如何減少回流和重繪。
最小化重繪和重排由于重繪和重排可能代價比較昂貴,因此最好就是可以減少它的發生次數。為了減少發生次數,我們可以合并多次對DOM和樣式的修改,然后一次處理掉。考慮這個例子
const el = document.getElementById("test"); el.style.padding = "5px"; el.style.borderLeft = "1px"; el.style.borderRight = "2px";
例子中,有三個樣式屬性被修改了,每一個都會影響元素的幾何結構,引起回流。當然,大部分現代瀏覽器都對其做了優化,因此,只會觸發一次重排。但是如果在舊版的瀏覽器或者在上面代碼執行的時候,有其他代碼訪問了布局信息(上文中的會觸發回流的布局信息),那么就會導致三次重排。
因此,我們可以合并所有的改變然后依次處理,比如我們可以采取以下的方式:
使用cssText
const el = document.getElementById("test"); el.style.cssText += "border-left: 1px; border-right: 2px; padding: 5px;";
修改CSS的class
const el = document.getElementById("test"); el.className += " active";批量修改DOM
當我們需要對DOM對一系列修改的時候,可以通過以下步驟減少回流重繪次數:
使元素脫離文檔流
對其進行多次修改
將元素帶回到文檔中。
該過程的第一步和第三步可能會引起回流,但是經過第一步之后,對DOM的所有修改都不會引起回流,因為它已經不在渲染樹了。
有三種方式可以讓DOM脫離文檔流:
隱藏元素,應用修改,重新顯示
使用文檔片段(document fragment)在當前DOM之外構建一個子樹,再把它拷貝回文檔。
將原始元素拷貝到一個脫離文檔的節點中,修改節點后,再替換原始的元素。
考慮我們要執行一段批量插入節點的代碼:
function appendDataToElement(appendToElement, data) { let li; for (let i = 0; i < data.length; i++) { li = document.createElement("li"); li.textContent = "text"; appendToElement.appendChild(li); } } const ul = document.getElementById("list"); appendDataToElement(ul, data);
如果我們直接這樣執行的話,由于每次循環都會插入一個新的節點,會導致瀏覽器回流一次。
我們可以使用這三種方式進行優化:
隱藏元素,應用修改,重新顯示
這個會在展示和隱藏節點的時候,產生兩次重繪
function appendDataToElement(appendToElement, data) { let li; for (let i = 0; i < data.length; i++) { li = document.createElement("li"); li.textContent = "text"; appendToElement.appendChild(li); } } const ul = document.getElementById("list"); ul.style.display = "none"; appendDataToElement(ul, data); ul.style.display = "block";
使用文檔片段(document fragment)在當前DOM之外構建一個子樹,再把它拷貝回文檔
const ul = document.getElementById("list"); const fragment = document.createDocumentFragment(); appendDataToElement(fragment, data); ul.appendChild(fragment);
將原始元素拷貝到一個脫離文檔的節點中,修改節點后,再替換原始的元素。
const ul = document.getElementById("list"); const clone = ul.cloneNode(true); appendDataToElement(clone, data); ul.parentNode.replaceChild(clone, ul);
對于上述那種情況,我寫了一個demo來測試修改前和修改后的性能。然而實驗結果不是很理想。
原因:原因其實上面也說過了,瀏覽器會使用隊列來儲存多次修改,進行優化,所以對這個優化方案,我們其實不用優先考慮。
避免觸發同步布局事件上文我們說過,當我們訪問元素的一些屬性的時候,會導致瀏覽器強制清空隊列,進行強制同步布局。舉個例子,比如說我們想將一個p標簽數組的寬度賦值為一個元素的寬度,我們可能寫出這樣的代碼:
function initP() { for (let i = 0; i < paragraphs.length; i++) { paragraphs[i].style.width = box.offsetWidth + "px"; } }
這段代碼看上去是沒有什么問題,可是其實會造成很大的性能問題。在每次循環的時候,都讀取了box的一個offsetWidth屬性值,然后利用它來更新p標簽的width屬性。這就導致了每一次循環的時候,瀏覽器都必須先使上一次循環中的樣式更新操作生效,才能響應本次循環的樣式讀取操作。每一次循環都會強制瀏覽器刷新隊列。我們可以優化為:
const width = box.offsetWidth; function initP() { for (let i = 0; i < paragraphs.length; i++) { paragraphs[i].style.width = width + "px"; } }
同樣,我也寫了個demo來比較兩者的性能差異。你可以自己點開這個demo體驗下。這個對比差距就比較明顯。
對于復雜動畫效果,使用絕對定位讓其脫離文檔流對于復雜動畫效果,由于會經常的引起回流重繪,因此,我們可以使用絕對定位,讓它脫離文檔流。否則會引起父元素以及后續元素頻繁的回流。這個我們就直接上個例子。
打開這個例子后,我們可以打開控制臺,控制臺上會輸出當前的幀數(雖然不準)。
從上圖中,我們可以看到,幀數一直都沒到60。這個時候,只要我們點擊一下那個按鈕,把這個元素設置為絕對定位,幀數就可以穩定60。
css3硬件加速(GPU加速)比起考慮如何減少回流重繪,我們更期望的是,根本不要回流重繪。這個時候,css3硬件加速就閃亮登場啦!!
劃重點:使用css3硬件加速,可以讓transform、opacity、filters這些動畫不會引起回流重繪 。但是對于動畫的其它屬性,比如background-color這些,還是會引起回流重繪的,不過它還是可以提升這些動畫的性能。
本篇文章只討論如何使用,暫不考慮其原理,之后有空會另外開篇文章說明。
如何使用常見的觸發硬件加速的css屬性:
transform
opacity
filters
Will-change
效果我們可以先看個例子。我通過使用chrome的Performance捕獲了一段時間的回流重繪情況,實際結果如下圖:
從圖中我們可以看出,在動畫進行的時候,沒有發生任何的回流重繪。如果感興趣你也可以自己做下實驗。
重點使用css3硬件加速,可以讓transform、opacity、filters這些動畫不會引起回流重繪
對于動畫的其它屬性,比如background-color這些,還是會引起回流重繪的,不過它還是可以提升這些動畫的性能。
css3硬件加速的坑如果你為太多元素使用css3硬件加速,會導致內存占用較大,會有性能問題。
在GPU渲染字體會導致抗鋸齒無效。這是因為GPU和CPU的算法不同。因此如果你不在動畫結束的時候關閉硬件加速,會產生字體模糊。
總結本文主要講了瀏覽器的渲染過程、瀏覽器的優化機制以及如何減少甚至避免回流和重繪,希望可以幫助大家更好的理解回流重繪。
參考文獻渲染樹構建、布局及繪制
高性能Javascript
本文地址在->本人博客地址, 歡迎給個 start 或 follow
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/53437.html
摘要:否則會引起父元素以及后續元素頻繁的回流。硬件加速加速硬件加速加速比起考慮如何減少回流重繪,我們更期望的是,根本不要回流重繪。這個時候,硬件加速就閃亮登場啦劃重點使用硬件加速,可以讓這些動畫不會引起回流重繪。回流和重繪可以說是每一個web開發者都經常聽到的兩個詞語,我也不例外,可是一直不是很清楚這兩步具體做了什么事情。最近由于部門內部要做分享,所以對其進行了一些研究,看了一些博客和書籍,整理了...
摘要:硬件加速加速比起考慮如何減少回流重繪,我們更期望的是,根本不要回流重繪。這個時候,硬件加速就閃亮登場啦劃重點使用硬件加速,可以讓這些動畫不會引起回流重繪。 本文由云+社區發表 回流和重繪可以說是每一個web開發者都經常聽到的兩個詞語,可是可能有很多人不是很清楚這兩步具體做了什么事情。最近有空對其進行了一些研究,看了一些博客和書籍,整理了一些內容并且結合一些例子,寫了這篇文章,希望可以幫...
摘要:硬件加速加速比起考慮如何減少回流重繪,我們更期望的是,根本不要回流重繪。這個時候,硬件加速就閃亮登場啦劃重點使用硬件加速,可以讓這些動畫不會引起回流重繪。 本文由云+社區發表 回流和重繪可以說是每一個web開發者都經常聽到的兩個詞語,可是可能有很多人不是很清楚這兩步具體做了什么事情。最近有空對其進行了一些研究,看了一些博客和書籍,整理了一些內容并且結合一些例子,寫了這篇文章,希望可以幫...
摘要:回流也被稱為重排,其實從字面上來看,重排更容易讓人形象易懂即重新排版整個頁面。重繪當頁面元素樣式改變不影響元素在文檔流中的位置時如,,,瀏覽器只會將新樣式賦予元素并進行重新繪制操作。你真的了解回流和重繪嗎 簡單先了解一下瀏覽器的渲染過程(圖片來自于網絡) showImg(https://segmentfault.com/img/bVbaC2e?w=624&h=289); 瀏覽器生成渲染...
閱讀 2077·2021-11-23 10:13
閱讀 2787·2021-11-09 09:47
閱讀 2737·2021-09-22 15:08
閱讀 3311·2021-09-03 10:46
閱讀 2229·2019-08-30 15:54
閱讀 908·2019-08-28 18:09
閱讀 2428·2019-08-26 18:26
閱讀 2340·2019-08-26 13:48