读书笔记 --- 再次阅读回流与重绘
參考 - 強烈推薦看看,這個作者寫了很多特別好的文章.
瀏覽器渲染過程
生成渲染樹
為了構建渲染樹,瀏覽器主要完成了以下工作:
【不可見的節點】:
- 一些不會渲染輸出的節點: 比如script、meta、link等
- 一些通過css進行隱藏的節點。比如display: none。注意,利用visibility和opacity隱藏的節點,還是會顯示在渲染樹上的。只有display:none的節點才不會顯示在渲染樹上
【注意】: 渲染樹只包括可見的節點
回流(Layout)
前面將DOM節點以及它對應的樣式結合起來,可是我們還需要計算它們在設備視口(viewport)內的確切位置和大小,這個計算的階段就是回流??聪旅娴睦踝?
<!DOCTYPE html> <html><head><meta name="viewport" content="width=device-width,initial-scale=1"><title>Cretical Path: Hello Marron!</title></head><body><div style="width: 50%"><div style="width: 50%">Hi Marron, best wish!</div></div></body> </html>我們可以看到,第一個div將節點的顯示尺寸設置為視口寬度的50%,第二個div將其尺寸設置為父節點的50%.而在回流這個階段,我們就需要根據視口具體的寬度,將其轉為實際的像素值。
?
重繪 (Painting)
- 生成渲染樹階段: 我們直到了哪些節點是可見的以及可見節點的樣式
- 在回流階段: 我們得到了可見元素的具體幾何信息
我們得到的信息,最終都會托付給GPU進行渲染
GPU的渲染需要具體的像素位置,這就是重繪階段所做的事情: 根據渲染樹和幾何信息計算出絕對像素點.
何時發生回流重繪
回流主要是計算節點的幾何位置和幾何像素大小.那么當頁面布局和幾何信息發生變化的時候,就需要回流:
- 添加或刪除可見的DOM元素
- 元素的位置發生變化
- 元素的尺寸發生變化(內/外邊距、邊框大小、高度和寬度等)
- 內容發生: 文本發生變化或圖片被另一個不同尺寸的圖片所替代
- 頁面剛開始渲染的時候
- 瀏覽器的窗口尺寸變化: 回流是根據視口的大小來計算元素的位置和大小的
經典老話: 回流一定重繪,重繪不一定回流
瀏覽器的優化機制
現代的瀏覽器都是很聰明的,由于每次重排都會造成造成額外的計算消耗,因此大多數瀏覽器都會通過隊列修改、批量執行來優化重排過程。瀏覽器會將修改操作放在隊列里,直到過了一段時間,或者操作達到一個閾值,才清空隊列。
還有一些強制刷新的屬性(避免使用):
- offsetTop、offsetLeft、offsetWidth、offsetHeight
- scrollTop、scrollLeft、scrollWidth、scrollHeight
- clientTop、clientLeft、clientWidth、clientHeight
- getComputedStyle()
- getBoundingClientRect
- …
前端優化
1 -【并多次的DOM和添加樣式】
// 未優化前 - 3次 const el = document.getElementById('test') el.style.padding = '5px'; el.style.borderLeft = '1px'; el.style.borderRight = '2px';// 合并樣式 - 1次 const el = document.getElementById('test'); el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px'// 添加樣式 - 1次 const el = document.getElementById('test'); el.calssName += ' active';2 -【脫離文檔流】
當元素脫離文檔流后,對元素的所有操作都不會引起回流和重繪.因此如果,對某個元素進行的DOM操作比較多的時候,可以先將元素脫離文檔流,然后操作,最后在放回文檔流。具體操作如下:
[注] : 上述的1、3會引起回流和重繪.
【脫離文檔流的方法】
- 隱藏元素,修改應用,重新顯示
- 使用文檔片段(document fragment)在使用DOM之外構建一個子樹,再把它拷貝回文檔
- 將原始元素拷貝到一個脫離文檔的節點中,修改節點后,再替換原始的元素。
[隱藏元素]
// 僅在隱藏元素和現實元素時產生2次回流和重繪 function appendDataToElement(appendToElement, data) {let li;for(let i =0, len = data.length; i < len; 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';[使用文檔片段] - 在當前DOM外構建一個子樹,再把它拷貝回文檔
const ul = document.getElementById('list'); const fragment = document.createDocumentFragment(); appendDataToElement(fragment, data); up.appendChild(fragment);[脫離文檔] - 將原始元素拷貝到一個脫離文檔的節點中,修改節點,再替換原始的元素。
const ul = document.getElementById('list'); const clone = ul.cloneNode(true); appendDataToElement(clone, data); ul.parentNode.replaceChild(clone, ul);[注] - 現代瀏覽器使用了隊列來存儲多次修改,因此上述的優化可能效果不是很理想.
3 - 【避免觸發同步布局事件】
// 栗子: 多次使用到 offsetWidth 屬性 function initP(){for(let i = 0; i< paragraph.length; i++){paragraph[i].style.width = box.offsetWidth + 'px'} }上述代碼每次循環,都會使瀏覽器強制刷新隊列(box.offsetWidth),造成多次回流和重繪.改進如下:
const width = box.offsetWidth; function initP(){for(let i = 0; i < paragraph.length; i++){paragraph[i].style.width = width + 'px'} }4 - 【復雜動畫的優化】
對于復雜動畫效果,由于會經常的引起回流和重繪。因此,我們可以使用絕對定位,讓它脫離文檔流。否則會引起父元素以及后續元素頻繁的回流 - 栗子
總結
以上是生活随笔為你收集整理的读书笔记 --- 再次阅读回流与重绘的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HTTP --- HTTP2小结
- 下一篇: NSGA_2总结梳理附代码按行详细注解