渲染
TL;DR
渲染流程
- 透過解析 HTML 建構 DOM Tree。
- 透過解析 CSS 建構 CSSOM Tree。
- 合併 DOM Tree 和 CSSOM Tree 建構 Render Tree。
- Layout (Reflow): 計算每個節點在瀏覽器的確切位置和大小。
- Paint (Repaint): 將 Render Tree 中的元素轉換為實際的像素。
- Composite: 將各個元素的像素合併在一起,形成最終的渲染畫面。
渲染流程
- 建構 DOM Tree
- 透過解析 HTML 建構而成。
- 建構 CSSOM Tree
- 透過解析 CSS 建構而成。
- 建構 Render Tree
- 從 DOM Tree 的根元素開始遍歷每一個可視節點。
note
有些節點不會顯示在瀏覽器上,例如: <script>
、<meta>
。
有些透過 CSS 隱藏的節點也不會顯示在瀏覽器上,例如 display: none
。
- 每一個 DOM Tree 上的可視節點會與 CSSOM Tree 上對應的節點合併成 Render Tree。
- Layout (Reflow)
- 從 Render Tree 根元素開始計算出每一個元素的確切位置和大小,需要耗費大量的運算資源。
- Paint (Repaint)
- 將 Render Tree 中的元素轉換為實際的像素。
- Composite
- 瀏覽器將各個元素的像素合併在一起,形成最終的渲染畫面。
Reflow
Reflow 與元素的佈局有關,元素的佈局和幾何屬性改變時,就會觸發 Reflow。觸發 Reflow 的屬性有:
- box model 相關屬性:
width
、height
、margin
、display
、border
... - 定位及浮動屬性:
position
、top
、float
... - 文字結構相關屬性:
text-align
、overflow
、font-size
...
除了這三種 CSS 屬性會觸發 reflow 外,以下情況也會觸發:
- 調整瀏覽器視窗大小
- DOM 操作
- 取得元素的
offsetWidth
、offsetHeight
、clientWidth
、clientHeight
、width
、height
、scrollTop
、scrollHeight
Repaint
元素的樣式屬性改變時,就會觸發 Repaint。會觸發 Repaint 的屬性有:
background
、color
、visibility
...note由頁面的渲染過程可知, Reflow 必定會觸發 Repaint,然而 Repaint 不會觸發 Reflow。
例如:
修改
width
→ Reflow → Repaint修改
color
→ Repaint
優化
1. 屬性替換
- 使用
translate
取代top
等定位屬性
2. 減少 reflow, repaint 操作
- 直接修改
classname
或cssText
。
// ❌ 2次 reflow 操作
element.style.left = '1px';
element.style.top = '2px';
// ✅ 1次 reflow 操作
element.className += ' className1';
使用
cloneNode()
複製一份 DOM,在上面修改樣式後,在替換原本的 DOM。使用
createDocumentFragment()
建立一份暫時的 DOM,再對真實的 DOM 做一次操作。
document.addEventListener('DOMContentLoaded', function () {
const fragment = document.createDocumentFragment();
for (let i = 0; i < 7000; i++) {
const node = document.createElement('div');
node.innerHTML = 'test' + i;
fragment.appendChild(node);
}
document.body.appendChild(fragment);
});
- 減少取得元素的 的次數
瀏覽器為了取得真實的值,當以下屬性每次被訪問時,都會造成 reflow:
- offsetTop/Left/Width/Height
- scrollTop/Left/Width/Height
- clientTop/Left/Width/Height
// ❌
for (let i = 0; i < 10; i++) {
element.style.left = element.offsetLeft + 5 + 'px';
element.style.top = element.offsetTop + 5 + 'px';
}
// ✅
let left = element.offsetLeft;
let top = element.offsetTop;
let s = element.style;
for (let i = 0; i < 20; i++) {
left += 5;
top += 5;
s.left = left + 'px';
s.top = top + 'px';
}
3. 減少影響範圍
- 將改動頻繁的範圍建立單獨的圖層
參考來源: