流必將引起重繪,重繪不一定會引起回流。
瀏覽器會把HTML解析成DOM,把CSS解析成CSSOM CSS Object Model即 CSSOM 和DOM合并就產生了Render Tree
當Render Tree中部分或全部元素的尺寸、結構、或某些屬性發生改變時,瀏覽器重新渲染部分或全部文檔的過程稱為回流
會導致回流的操作:
頁面首次渲染
瀏覽器窗口大小發生改變
元素尺寸或位置發生改變
元素內容變化(文字數量或圖片大小等等)
元素字體大小變化
添加或者刪除可見的DOM元素
激活CSS偽類(例如::hover)
查詢某些屬性或調用某些方法
一些常用且會導致回流的屬性和方法:
clientWidth、clientHeight、clientTop、clientLeft
offsetWidth、offsetHeight、offsetTop、offsetLeft
scrollWidth、scrollHeight、scrollTop、scrollLeft
scrollIntoView()、scrollIntoViewIfNeeded()
getComputedStyle()
getBoundingClientRect()
scrollTo()
當頁面中元素樣式的改變并不影響它在文檔流中的位置時(例如:color、background-color、visibility等),瀏覽器會將新樣式賦予給元素并重新繪制它,這個過程稱為重繪。
!!!回流比重繪的代價要更高
當你訪問以下屬性或方法時,瀏覽器會立刻清空隊列:
clientWidth、clientHeight、clientTop、clientLeft
offsetWidth、offsetHeight、offsetTop、offsetLeft
scrollWidth、scrollHeight、scrollTop、scrollLeft
width、height
getComputedStyle()
getBoundingClientRect()
如何避免
CSS
避免使用table布局。
盡可能在DOM樹的最末端改變class。
避免設置多層內聯樣式。
將動畫效果應用到position屬性為absolute或fixed的元素上。
避免使用CSS表達式(例如:calc())。
JavaScript
避免頻繁操作樣式,最好一次性重寫style屬性,或者將樣式列表定義為class并一次性更改class屬性。
避免頻繁操作DOM,創建一個documentFragment,在它上面應用所有DOM操作,最后再把它添加到文檔中。
也可以先為元素設置display: none,操作結束后再把它顯示出來。因為在display屬性為none的元素上進行的DOM操作不會引發回流和重繪。
避免頻繁讀取會引發回流/重繪的屬性,如果確實需要多次使用,就用一個變量緩存起來。
對具有復雜動畫的元素使用絕對定位,使它脫離文檔流,否則會引起父元素及后續元素頻繁回流
能優化中,減少重繪重排應該是一種很好的優化方式,我們具體看一下什么情況下會造成重繪重排,為什么減少重繪重排可以做到優化,怎么樣減少重繪重排。
我們先看看當瀏覽器拿到服務端返回的資源時,是如何渲染的。
首先瀏覽器會進行文件解析,主要解析三個東西:
解析完成之后
布局完成之后,就要進行繪制了,將各層發給 GPU,GPU 將各層合成,顯示在屏幕上,即 composite。
當我們開始繪制的時候,如果使用 js 操作了 dom 元素,或者改變了 css 屬性,就可能會造成重繪(repaint)和重排(reflow)。
repaint:屏幕的一部分進行了重畫,比如某個 css 中改變背景色,元素尺寸沒有變。 reflow:任何一個元素的尺寸發生了變化,需要重新驗證并計算 render tree,就會造成重排。
在 PC 時代,我們用 jquery 進行獲取元素,改變元素的尺寸,及時發生重排,我們也很難感知到,但是當移動時代到來之后,如果頻繁發生重排,那手機就會受不了了。
尤其是在執行下面操作時,成本會很高:
如果發生上述的行為基本都會造成重繪和重排。
所以,當發生重排時,一定會發生重繪,但是發生重繪不一定會發生重排。
瀏覽器中每個元素節點都有 reflow 方法,當一個元素發生 reflow 時,他的子節點都會發生 reflow。
舉幾個例子來說明一下造成重繪重排的情況:
var bodyStyle=document.body.style; // cache bodyStyle.padding='20px'; // reflow, repaint bodyStyle.border='10px solid red'; // 再一次的 reflow 和 repaint bodyStyle.color='blue'; // repaint bodyStyle.backgroundColor='#fad'; // repaint bodyStyle.fontSize='2em'; // reflow, repaint // new DOM element - reflow, repaint document.body.appendChild(document.createTextNode('children!'));
前面說了瀏覽器的渲染機制,多一次重繪就需要瀏覽器重新進行一次繪制,及時 GPU 處理會比較快,但是也是吃不消的,更別說重排了,重排一個 dom,會重新生成 render Tree,然后重新繪制。
其實瀏覽器很聰明,不可能每次修改樣式就 reflow 或者 repaint 一次,一般來說,瀏覽器會積累一批操作,然后做一次 reflow。
但是也有些例外情況,比如 resize 窗口,改變窗口字體,瀏覽器會立即進行 reflow。
雖然瀏覽器會這么做,但是我們也應該減少重繪重排的次數,在開發階段就為瀏覽器進行特殊的關愛,畢竟是每天陪伴我們的小伙伴。
下面總結了一些針對 reflow 和 repaint 的最佳實踐:
當每次布局完成之后,就會發生 composite 過程,瀏覽器都把重繪后的圖像發給 GPU 去合成并顯示。
在上面最佳實踐中最后提到了動畫,動畫其實是比較耗費性能的,因為動畫的每一幀都會發給 GPU 去合成,重繪重排會發生在動畫的每一幀。
我們在寫動畫的時候,可以通過 js 寫,也可以通過 css 寫。兩種方式在寫動畫時,過程也是不一樣的。
所以不難看出,耗費性能最少并能并最流暢的動畫是只觸發合成。
為了僅發生 composite,我們做動畫的 css property 必須滿足以下三個條件:
滿足以上以上條件的 css property 只有 transform 和 opacity。
這樣的話,由于沒有重排和重繪,只有合成,那么瀏覽器在動畫執行之前就知道動畫如何開始和結束。
并且有兩個優勢:
事實上影響動畫流暢性的因素不止重排重繪,還有 CPU 內存。
css 動畫有一個重要的特性,它是完全工作在 GPU 上。因為你聲明了一個動畫如何開始和如何結束,瀏覽器會在動畫開始前準備好所有需要的指令;并把它們發送給 GPU。
而如果使用 js 動畫,瀏覽器必須計算每一幀的狀態;為了保證平滑的動畫,我們必須在瀏覽器主線程計算新狀態;把它們發送給 GPU 至少 60 次每秒。
除了計算和發送數據比 css 動畫要慢,主線程的負載也會影響動畫; 當主線程的計算任務過多時,會造成動畫的延遲、卡頓。
所以最佳實踐中最后一條就提到了,在寫動畫時,盡量寫 css 動畫,并且盡量用 transform 和 opacity。
谷歌瀏覽器檢測重繪工具:右上角三點->更多工具->開發者工具->Performance。
chrome瀏覽器的Performance是頁面性能分析的利器,網上有很多關于關于如何去使用和查看Performance的文章,這里就不多做闡述了,大伙可以多去了解了解。
總之,頁面性能優化是前端從初級到高級都避不開的一個話題,如何做到性能的最優化更是一個資深前端應該考慮的事情,這里也希望有更好更多見解的小伙伴能夠私聊我,給我點意見。
文分享自華為云社區《前端頁面之“回流重繪”-云社區-華為云》,作者:CoderBin。
在HTML中,每個元素都可以理解成一個盒子,在瀏覽器解析過程中,會涉及到回流與重繪:
具體的瀏覽器解析渲染機制如下所示:
在頁面初始渲染階段,回流不可避免的觸發,可以理解成頁面一開始是空白的元素,后面添加了新的元素使頁面布局發生改變
當我們對 DOM 的修改引發了 DOM幾何尺寸的變化(比如修改元素的寬、高或隱藏元素等)時,瀏覽器需要重新計算元素的幾何屬性,然后再將計算的結果繪制出來
當我們對 DOM的修改導致了樣式的變化(color或background-color),卻并未影響其幾何屬性時,瀏覽器不需重新計算元素的幾何屬性、直接為該元素繪制新的樣式,這里就僅僅觸發了回流
要想減少回流和重繪的次數,首先要了解回流和重繪是如何觸發的
回流這一階段主要是計算節點的位置和幾何信息,那么當頁面布局和幾何信息發生變化的時候,就需要回流,如下面情況:
還有一些容易被忽略的操作:獲取一些特定屬性的值
offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight
這些屬性有一個共性,就是需要通過即時計算得到。因此瀏覽器為了獲取這些值,也會進行回流
除此還包括getComputedStyle方法,原理是一樣的
觸發回流一定會觸發重繪
可以把頁面理解為一個黑板,黑板上有一朵畫好的小花。現在我們要把這朵從左邊移到了右邊,那我們要先確定好右邊的具體位置,畫好形狀(回流),再畫上它原有的顏色(重繪)
除此之外還有一些其他引起重繪行為:
由于每次重排都會造成額外的計算消耗,因此大多數瀏覽器都會通過隊列化修改并批量執行來優化重排過程。瀏覽器會將修改操作放入到隊列里,直到過了一段時間或者操作達到了一個閾值,才清空隊列
當你獲取布局信息的操作的時候,會強制隊列刷新,包括前面講到的offsetTop等方法都會返回最新的數據
因此瀏覽器不得不清空隊列,觸發回流重繪來返回正確的值
我們了解了如何觸發回流和重繪的場景,下面給出避免回流的經驗:
在使用 JavaScript 動態插入多個節點時, 可以使用DocumentFragment. 創建后一次插入. 就能避免多次的渲染性能
但有時候,我們會無可避免地進行回流或者重繪,我們可以更好使用它們
例如,多次修改一個把元素布局的時候,我們很可能會如下操作
const el=document.getElementById('el')
for(let i=0;i<10;i++) {
el.style.top=el.offsetTop + 10 + "px";
el.style.left=el.offsetLeft + 10 + "px";
}
每次循環都需要獲取多次offset屬性,比較糟糕,可以使用變量的形式緩存起來,待計算完畢再提交給瀏覽器發出重計算請求
// 緩存offsetLeft與offsetTop的值
const el=document.getElementById('el')
let offLeft=el.offsetLeft, offTop=el.offsetTop
// 在JS層面進行計算
for(let i=0;i<10;i++) {
offLeft +=10
offTop +=10
}
// 一次性將計算結果應用到DOM上
el.style.left=offLeft + "px"
el.style.top=offTop + "px"
我們還可避免改變樣式,使用類名去合并樣式
const container=document.getElementById('container')
container.style.width='100px'
container.style.height='200px'
container.style.border='10px solid red'
container.style.color='red'
使用類名去合并樣式
<style>
.basic_style {
width: 100px;
height: 200px;
border: 10px solid red;
color: red;
}
</style>
<script>
const container=document.getElementById('container')
container.classList.add('basic_style')
</script>
前者每次單獨操作,都去觸發一次渲染樹更改(新瀏覽器不會),
都去觸發一次渲染樹更改,從而導致相應的回流與重繪過程
合并之后,等于我們將所有的更改一次性發出
我們還可以通過通過設置元素屬性display: none,將其從頁面上去掉,然后再進行后續操作,這些后續操作也不會觸發回流與重繪,這個過程稱為離線操作
點擊下方,第一時間了解華為云新鮮技術~
華為云博客_大數據博客_AI博客_云計算博客_開發者中心-華為云
*請認真填寫需求信息,我們會在24小時內與您取得聯系。