題外話:有時候呢,學着學着就卡機了>-<....又到了記小本本時刻。
目錄
1、浏覽器渲染大緻流程
2、生成渲染樹
3、回流(reflow)
3-1、定義
3-2、會導緻回流的操作
3-3、常用且會導緻回流的屬性和方法
4、重繪(repaint)
4-1、定義
4-2、性能影響
4-3、浏覽器會立刻清空隊列的通路屬性或方法
5、優化
5-1、CSS
5-2、JS
5-3、DocumentFragment
首先,先回顧下浏覽器渲染過程吧!
1、浏覽器渲染大緻流程
- 處理 HTML 标記并構造 DOM 樹。
- 處理 CSS 标記并構造 CSSOM 樹。
- 将 DOM 與 CSSOM 合并成一個渲染樹。
- 根據渲染樹來布局,以計算每個節點的幾何資訊。
- 将各個幾點繪制到螢幕上。/
2、生成渲染樹
建構渲染樹,浏覽器需要做一下工作:
- 從 DOM 樹的根節點開始周遊每個 可見 節點。
- 對于每個可見的節點,找到 CSSOM 樹中對應的規則,并應用它們。
- 根據每個可見節點以及其對應的樣式,組合生成渲染樹。
第一步周遊節點的時候,需要知道什麼節點是不可見的。
- 一些不會渲染輸出的節點。比如 script、meta、link等。
- 某些節點通過 CSS 隐藏。注意 visibility: hidden 與 display: none 是不一樣的。
- 前者隐藏元素,但元素仍占據着布局空間(即将其渲染成一個空框),而後者 (display: none) 将元素從渲染樹中完全移除,元素既不可見,也不是布局的組成部分。
- 注意: 渲染樹隻包含可見的節點
3、回流(reflow)
當我們對 DOM 的修改引發了 DOM 幾何尺寸的變化(比如修改元素的寬、高或隐藏元素等)時,浏覽器需要重新計算元素的幾何屬性(其他元素的幾何屬性和位置也會是以受到影響),然後再将計算的結果繪制出來。這個過程就是回流(也叫重排)。
3-1、定義
元素的大小或者位置發生改變(當頁面布局發生改變的時候),觸發了重新布局導緻渲染樹重新計算布局和渲染。
- 如添加或删除可見的DOM元素
- 元素的位置發生變化
- 元素的尺寸發生變化
- 内容發生變化(如文本變化或圖檔被另一個不同尺寸的圖檔所代替)
- 頁面一開始渲染的時候(無法避免)
3-2、會導緻回流的操作
- 頁面首次渲染。
- 浏覽器視窗大小發生改變。
- 元素尺寸或者位置發生改變。
- 元素内容變化(文字數量或者圖檔大小發生改變)。
- 元素字型大小的改變。
- 添加或者删除可見的
元素。DOM
- 激活
僞類 (eg:CSS
)。:hover
- 查詢某些屬性或調用某些方法。
3-3、常用且會導緻回流的屬性和方法
-
、clientWidth
、clientHeight
、clientTop
clientLeft
-
、offsetWidth
、offsetHeight
、offsetTop
offsetLeft
-
、scrollWidth
、scrollHeight
、scrollTop
scrollLeft
-
、scrollIntoView()
scrollIntoViewIfNeeded()、scrollTo()
-
、getComputedStyle()
getBoundingClientRect()
4、重繪(repaint)
當我們對 DOM 的修改導緻了樣式的變化、卻并未影響其幾何屬性(比如修改了顔色或背景色)時,浏覽器不需重新計算元素的幾何屬性、直接為該元素繪制新的樣式(跳過了上圖所示的回流環節)。這個過程叫做重繪。
4-1、定義
隻改變自身樣式,不會影響到其他元素。
- 元素樣式的改變(但寬高、大小、位置不變)
- eg: visibility、color、background-color等
注意:回流一定會觸發重繪,而重繪不一定會回流
4-2、性能影響
- 有時即使僅僅回流一個單一的元素,它的父元素以及任何跟随它的元素也會産生回流
- 現代浏覽器會對頻繁的回流或重繪操作進行優化:浏覽器會維護一個隊列,把所有引起回流和重繪的操作放入隊列中,如果隊列中的任務數量或者時間間隔達到一個門檻值的,浏覽器就會将隊列清空,進行一次批處理,這樣可以把多次回流和重繪變成一次。
4-3、浏覽器會立刻清空隊列的通路屬性或方法
當你通路以下屬性或方法時,浏覽器會立刻清空隊列:
-
、clientWidth
、clientHeight
、clientTop
clientLeft
-
、offsetWidth
、offsetHeight
、offsetTop
offsetLeft
-
、scrollWidth
、scrollHeight
、scrollTop
scrollLeft
-
、scrollIntoView()
scrollIntoViewIfNeeded()
-
getComputedStyle()
-
getBoundingClientRect()
-
scrollTo()
因為隊列中可能會有影響到這些屬性或方法傳回值的操作,即使你希望擷取的資訊與隊列中操作引發的改變無關,浏覽器也會強行清空隊列,確定你拿到的值是最精确的。
5、優化
- 減少對 render tree 的操作 【合并多次多DOM和樣式的修改】
- 減少對一些style資訊的請求,盡量利用好浏覽器的優化政策
- 添加css樣式而不是利用js控制樣式(我就是想到這種辦法解決回流問題的)
- 盡量将需要改變DOM的操作一次完成
- 直接改變className,如果動态改變樣式,則使用cssText(考慮沒有優化的浏覽器)
- 不要經常通路會引起浏覽器flush隊列的屬性,如果你确實要通路,利用緩存
- 讓元素脫離動畫流,減少回流的Render Tree的規模
- 将需要多次重排的元素,position屬性設為absolute或fixed,這樣此元素就脫離了文檔流,它的變化不會影響到其他元素。例如有動畫效果的元素就最好設定為絕對定位;
- 盡量不要使用表格布局,如果沒有定寬表格一列的寬度由最寬的一列決定,那麼很可能在最後一行的寬度超出之前的列寬,引起整體回流造成table可能需要多次計算才能确定好其在渲染樹中節點的屬性,通常要花3倍于同等元素的時間。
5-1、CSS
- 直接改變className. (把要修改的樣式集中到一個 class 内統一修改);
- 避免使用
布局 (盡量不要使用表格布局,如果沒有定寬表格一列的寬度由最寬的一列決定,那麼很可能在最後一行的寬度超出之前的列寬,引起整體回流造成table可能需要多次計算才能确定好其在渲染樹中節點的屬性,通常要花3倍于同等元素的時間。)table
- 盡可能在DOM樹的最末端改變class,盡可能在DOM樹的裡面改變class(可以限制回流的範圍)
- 将需要多次重排的元素,position屬性設為absolute或fixed,這樣此元素就脫離了文檔流,它的變化不會影響到其他元素。例如有動畫效果的元素就最好設定為絕對定位;
- 使用display:none技術,隻引發兩次回流和重繪;
5-2、JS
- 避免頻繁操作樣式,最好一次性重寫style屬性,或者将樣式清單定義為class并一次性更改class屬性。(而不是利用js控制樣式)
- 不要經常通路會引起浏覽器緩存隊列的屬性(上述那些浏覽器會立刻清空隊列的屬性)。如果确實要通路,利用緩存。eg:
// bad for (var i = 0; i < len; i++) { el.style.left = el.offsetLeft + x + "px"; el.style.top = el.offsetTop + y + "px"; } // good var x = el.offsetLeft, y = el.offsetTop; for (var i = 0; i < len; i++) { x += 10; y += 10; el.style = x + "px"; el.style = y + "px"; }
- 盡量将需要改變DOM的操作一次完成
let box = document.getElementById("box").style; // bad box.color = "red"; // 重繪 box.size = "14px"; // 回流、重繪 // good box.bord = '1px solid red'
- 對具有複雜動畫的元素使用絕對定位,使它脫離文檔流,否則會引起父元素及後續元素頻繁回流
- 避免頻繁操作
,建立一個DOM
,在它上面應用有documentFragment
操作,最後再把它添加到文檔中DOM
5-3、DocumentFragment
- 文檔片段接口,表示一個沒有父級檔案的最小文檔對象
- 與
最大的差別是因為Document
不是真實DocumentFragment
樹的一部分,它的變化不會觸發DOM
樹的(重新渲染) ,且不會導緻性能等問題DOM
- 可以使用
方法或者構造函數來建立一個空的document.createDocumentFragment
DocumentFragment。