怎么掌握瀏覽器回流和重繪

本篇內(nèi)容主要講解“怎么掌握瀏覽器回流和重繪”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“怎么掌握瀏覽器回流和重繪”吧!

網(wǎng)站建設(shè)公司,為您提供網(wǎng)站建設(shè),網(wǎng)站制作,網(wǎng)頁設(shè)計及定制網(wǎng)站建設(shè)服務(wù),專注于企業(yè)網(wǎng)站制作,高端網(wǎng)頁制作,對玻璃隔斷等多個行業(yè)擁有豐富的網(wǎng)站建設(shè)經(jīng)驗的網(wǎng)站建設(shè)公司。專業(yè)網(wǎng)站設(shè)計,網(wǎng)站優(yōu)化推廣哪家好,專業(yè)成都網(wǎng)站營銷優(yōu)化,H5建站,響應(yīng)式網(wǎng)站。

瀏覽器的渲染過程

本文先從瀏覽器的渲染過程來從頭到尾的講解一下回流重繪,如果大家想直接看如何減少回流和重繪,優(yōu)化性能,可以跳到后面。(這個渲染過程來自MDN)

怎么掌握瀏覽器回流和重繪

瀏覽器渲染過程

添加描述

從上面這個圖上,我們可以看到,瀏覽器渲染過程如下:

  1.  解析HTML,生成DOM樹,解析CSS,生成CSSOM樹

  2.  將DOM樹和CSSOM樹結(jié)合,生成渲染樹(Render Tree)

  3.  Layout(回流):根據(jù)生成的渲染樹,進行回流(Layout),得到節(jié)點的幾何信息(位置,大?。?/p>

  4.  Painting(重繪):根據(jù)渲染樹以及回流得到的幾何信息,得到節(jié)點的絕對像素

  5.  Display:將像素發(fā)送給GPU,展示在頁面上。(這一步其實還有很多內(nèi)容,比如會在GPU將多個合成層合并為同一個層,并展示在頁面中。而css3硬件加速的原理則是新建合成層,這里我們不展開,之后有機會會寫一篇博客)

渲染過程看起來很簡單,讓我們來具體了解下每一步具體做了什么。

生成渲染樹

怎么掌握瀏覽器回流和重繪

渲染樹構(gòu)建

為了構(gòu)建渲染樹,瀏覽器主要完成了以下工作:

  1.  從DOM樹的根節(jié)點開始遍歷每個可見節(jié)點。

  2.  對于每個可見的節(jié)點,找到CSSOM樹中對應(yīng)的規(guī)則,并應(yīng)用它們。

  3.  根據(jù)每個可見節(jié)點以及其對應(yīng)的樣式,組合生成渲染樹。

***步中,既然說到了要遍歷可見的節(jié)點,那么我們得先知道,什么節(jié)點是不可見的。不可見的節(jié)點包括:

  •  一些不會渲染輸出的節(jié)點,比如script、meta、link等。

  •  一些通過css進行隱藏的節(jié)點。比如display:none。注意,利用visibility和opacity隱藏的節(jié)點,還是會顯示在渲染樹上的。只有display:none的節(jié)點才不會顯示在渲染樹上。

從上面的例子來講,我們可以看到span標(biāo)簽的樣式有一個display:none,因此,它最終并沒有在渲染樹上。

注意:渲染樹只包含可見的節(jié)點

回流

前面我們通過構(gòu)造渲染樹,我們將可見DOM節(jié)點以及它對應(yīng)的樣式結(jié)合起來,可是我們還需要計算它們在設(shè)備視口(viewport)內(nèi)的確切位置和大小,這個計算的階段就是回流。

為了弄清每個對象在網(wǎng)站上的確切大小和位置,瀏覽器從渲染樹的根節(jié)點開始遍歷,我們可以以下面這個實例來表示:

<!DOCTYPE html>  <html>    <head>      <meta name="viewport" content="width=device-width,initial-scale=1">      <title>Critial Path: Hello world!</title>    </head>    <body>      <div style="width: 50%">        <div style="width: 50%">Hello world!</div>      </div>    </body>  </html>

我們可以看到,***個div將節(jié)點的顯示尺寸設(shè)置為視口寬度的50%,第二個div將其尺寸設(shè)置為父節(jié)點的50%。而在回流這個階段,我們就需要根據(jù)視口具體的寬度,將其轉(zhuǎn)為實際的像素值。(如下圖)

怎么掌握瀏覽器回流和重繪

回流

重繪

最終,我們通過構(gòu)造渲染樹和回流階段,我們知道了哪些節(jié)點是可見的,以及可見節(jié)點的樣式和具體的幾何信息(位置、大小),那么我們就可以將渲染樹的每個節(jié)點都轉(zhuǎn)換為屏幕上的實際像素,這個階段就叫做重繪節(jié)點。

既然知道了瀏覽器的渲染過程后,我們就來探討下,何時會發(fā)生回流重繪。

何時發(fā)生回流重繪

我們前面知道了,回流這一階段主要是計算節(jié)點的位置和幾何信息,那么當(dāng)頁面布局和幾何信息發(fā)生變化的時候,就需要回流。比如以下情況:

  •  添加或刪除可見的DOM元素

  •  元素的位置發(fā)生變化

  •  元素的尺寸發(fā)生變化(包括外邊距、內(nèi)邊框、邊框大小、高度和寬度等)

  •  內(nèi)容發(fā)生變化,比如文本變化或圖片被另一個不同尺寸的圖片所替代。

  •  頁面一開始渲染的時候(這肯定避免不了)

  •  瀏覽器的窗口尺寸變化(因為回流是根據(jù)視口的大小來計算元素的位置和大小的)

注意:回流一定會觸發(fā)重繪,而重繪不一定會回流

根據(jù)改變的范圍和程度,渲染樹中或大或小的部分需要重新計算,有些改變會觸發(fā)整個頁面的重排,比如,滾動條出現(xiàn)的時候或者修改了根節(jié)點。

瀏覽器的優(yōu)化機制

現(xiàn)代的瀏覽器都是很聰明的,由于每次重排都會造成額外的計算消耗,因此大多數(shù)瀏覽器都會通過隊列化修改并批量執(zhí)行來優(yōu)化重排過程。瀏覽器會將修改操作放入到隊列里,直到過了一段時間或者操作達到了一個閾值,才清空隊列。但是!當(dāng)你獲取布局信息的操作的時候,會強制隊列刷新,比如當(dāng)你訪問以下屬性或者使用以下方法:

  •  offsetTop、offsetLeft、offsetWidth、offsetHeight

  •  scrollTop、scrollLeft、scrollWidth、scrollHeight

  •  clientTop、clientLeft、clientWidth、clientHeight

  •  getComputedStyle()

  •  getBoundingClientRect

  •  具體可以訪問這個網(wǎng)站:https://gist.github.com/pauli...

以上屬性和方法都需要返回***的布局信息,因此瀏覽器不得不清空隊列,觸發(fā)回流重繪來返回正確的值。因此,我們在修改樣式的時候,***避免使用上面列出的屬性,他們都會刷新渲染隊列。如果要使用它們,***將值緩存起來。

減少回流和重繪

好了,到了我們今天的重頭戲,前面說了這么多背景和理論知識,接下來讓我們談?wù)勅绾螠p少回流和重繪。

最小化重繪和重排

由于重繪和重排可能代價比較昂貴,因此***就是可以減少它的發(fā)生次數(shù)。為了減少發(fā)生次數(shù),我們可以合并多次對DOM和樣式的修改,然后一次處理掉??紤]這個例子

const el = document.getElementById('test');  el.style.padding = '5px';  el.style.borderLeft = '1px';  el.style.borderRight = '2px';

例子中,有三個樣式屬性被修改了,每一個都會影響元素的幾何結(jié)構(gòu),引起回流。當(dāng)然,大部分現(xiàn)代瀏覽器都對其做了優(yōu)化,因此,只會觸發(fā)一次重排。但是如果在舊版的瀏覽器或者在上面代碼執(zhí)行的時候,有其他代碼訪問了布局信息(上文中的會觸發(fā)回流的布局信息),那么就會導(dǎo)致三次重排。

因此,我們可以合并所有的改變?nèi)缓笠来翁幚?,比如我們可以采取以下的方式?/p>

  •  使用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

當(dāng)我們需要對DOM對一系列修改的時候,可以通過以下步驟減少回流重繪次數(shù):

  1.  使元素脫離文檔流

  2.  對其進行多次修改

  3.  將元素帶回到文檔中。

該過程的***步和第三步可能會引起回流,但是經(jīng)過***步之后,對DOM的所有修改都不會引起回流重繪,因為它已經(jīng)不在渲染樹了。

有三種方式可以讓DOM脫離文檔流:

  •  隱藏元素,應(yīng)用修改,重新顯示

  •  使用文檔片段(document fragment)在當(dāng)前DOM之外構(gòu)建一個子樹,再把它拷貝回文檔。

  •  將原始元素拷貝到一個脫離文檔的節(jié)點中,修改節(jié)點后,再替換原始的元素。

考慮我們要執(zhí)行一段批量插入節(jié)點的代碼:

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

如果我們直接這樣執(zhí)行的話,由于每次循環(huán)都會插入一個新的節(jié)點,會導(dǎo)致瀏覽器回流一次。

我們可以使用這三種方式進行優(yōu)化:

隱藏元素,應(yīng)用修改,重新顯示

這個會在展示和隱藏節(jié)點的時候,產(chǎn)生兩次回流

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)在當(dāng)前DOM之外構(gòu)建一個子樹,再把它拷貝回文檔

const ul = document.getElementById('list');  const fragment = document.createDocumentFragment();  appendDataToElement(fragment, data);  ul.appendChild(fragment);

將原始元素拷貝到一個脫離文檔的節(jié)點中,修改節(jié)點后,再替換原始的元素。

const ul = document.getElementById('list');  const clone = ul.cloneNode(true);  appendDataToElement(clone, data);  ul.parentNode.replaceChild(clone, ul);

對于上面這三種情況,我寫了一個demo在safari和chrome上測試修改前和修改后的性能。然而實驗結(jié)果不是很理想。

原因:原因其實上面也說過了,現(xiàn)代瀏覽器會使用隊列來儲存多次修改,進行優(yōu)化,所以對這個優(yōu)化方案,我們其實不用優(yōu)先考慮。

避免觸發(fā)同步布局事件

上文我們說過,當(dāng)我們訪問元素的一些屬性的時候,會導(dǎo)致瀏覽器強制清空隊列,進行強制同步布局。舉個例子,比如說我們想將一個p標(biāo)簽數(shù)組的寬度賦值為一個元素的寬度,我們可能寫出這樣的代碼:

function initP() {      for (let i = 0; i < paragraphs.length; i++) {          paragraphs[i].style.width = box.offsetWidth + 'px';      }  }

這段代碼看上去是沒有什么問題,可是其實會造成很大的性能問題。在每次循環(huán)的時候,都讀取了box的一個offsetWidth屬性值,然后利用它來更新p標(biāo)簽的width屬性。這就導(dǎo)致了每一次循環(huán)的時候,瀏覽器都必須先使上一次循環(huán)中的樣式更新操作生效,才能響應(yīng)本次循環(huán)的樣式讀取操作。每一次循環(huán)都會強制瀏覽器刷新隊列。我們可以優(yōu)化為:

const width = box.offsetWidth;  function initP() {      for (let i = 0; i < paragraphs.length; i++) {          paragraphs[i].style.width = width + 'px';      }  }

同樣,我也寫了個demo來比較兩者的性能差異。你可以自己點開這個demo體驗下。這個對比的性能差距就比較明顯。

對于復(fù)雜動畫效果,使用絕對定位讓其脫離文檔流

對于復(fù)雜動畫效果,由于會經(jīng)常的引起回流重繪,因此,我們可以使用絕對定位,讓它脫離文檔流。否則會引起父元素以及后續(xù)元素頻繁的回流。這個我們就直接上個例子。

打開這個例子后,我們可以打開控制臺,控制臺上會輸出當(dāng)前的幀數(shù)(雖然不準(zhǔn))。

怎么掌握瀏覽器回流和重繪

添加描述

從上圖中,我們可以看到,幀數(shù)一直都沒到60。這個時候,只要我們點擊一下那個按鈕,把這個元素設(shè)置為絕對定位,幀數(shù)就可以穩(wěn)定60。

css3硬件加速(GPU加速)

比起考慮如何減少回流重繪,我們更期望的是,根本不要回流重繪。這個時候,css3硬件加速就閃亮登場啦?。?/p>

劃重點:

1. 使用css3硬件加速,可以讓transform、opacity、filters這些動畫不會引起回流重繪 。

2. 對于動畫的其它屬性,比如background-color這些,還是會引起回流重繪的,不過它還是可以提升這些動畫的性能。

本篇文章只討論如何使用,暫不考慮其原理,之后有空會另外開篇文章說明。

如何使用

常見的觸發(fā)硬件加速的css屬性:

  •  transform

  •  opacity

  •  filters

  •  Will-change

效果

我們可以先看個例子。我通過使用chrome的Performance捕獲了動畫一段時間里的回流重繪情況,實際結(jié)果如下圖:

怎么掌握瀏覽器回流和重繪

添加描述

從圖中我們可以看出,在動畫進行的時候,沒有發(fā)生任何的回流重繪。如果感興趣你也可以自己做下實驗。

重點

  •  使用css3硬件加速,可以讓transform、opacity、filters這些動畫不會引起回流重繪

  •  對于動畫的其它屬性,比如background-color這些,還是會引起回流重繪的,不過它還是可以提升這些動畫的性能。

css3硬件加速的坑

當(dāng)然,任何美好的東西都是會有對應(yīng)的代價的,過猶不及。css3硬件加速還是有坑的:

  •  如果你為太多元素使用css3硬件加速,會導(dǎo)致內(nèi)存占用較大,會有性能問題。

  •  在GPU渲染字體會導(dǎo)致抗鋸齒無效。這是因為GPU和CPU的算法不同。因此如果你不在動畫結(jié)束的時候關(guān)閉硬件加速,會產(chǎn)生字體模糊。

到此,相信大家對“怎么掌握瀏覽器回流和重繪”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

本文標(biāo)題:怎么掌握瀏覽器回流和重繪
轉(zhuǎn)載源于:http://muchs.cn/article20/ispijo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)網(wǎng)站制作、虛擬主機網(wǎng)站導(dǎo)航、企業(yè)建站、Google、全網(wǎng)營銷推廣

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

綿陽服務(wù)器托管