[TOC]
# 回流與重繪
回流:當render tree中的一部分(或全部)因為元素的規模**尺寸**,**布局**,**隱藏**等改變而需要重新構建。每個頁面至少需要一次回流,就是在頁面第一次加載的時候。在回流的時候,瀏覽器會使渲染樹中受到影響的部分失效,并重新構造這部分渲染樹,完成回流后,瀏覽器會重新繪制受影響的部分到屏幕中,該過程稱為重繪。
重繪:當render tree中的一些元素需要更新屬性,而這些屬性只是影響元素的外觀,風格,而不會影響布局的,比如background-color。
> 注意:回流必將引起重繪,而重繪不一定會引起回流。
>

* 藍色部分:HTML解析和網絡通信占用的時間
* 黃色部分:JavaScript語句執行所占用時間
* 紫色部分:重排占用時間
* 綠色部分:重繪占用時間
當頁面布局和幾何屬性改變時就需要回流。下述情況會發生瀏覽器回流:
1、添加或者刪除可見的DOM元素;
2、元素位置改變;
3、元素尺寸改變(margin、padding、border、width和height)
4、內容改變,尤其是輸入控件
5、頁面渲染初始化;
6、瀏覽器窗口尺寸改變(resize事件發生時);
7、改變文字大小;
8、激活偽類,如:hover;
9、offsetWidth, width, clientWidth, scrollTop/scrollHeight的計算, 會使瀏覽器將漸進回流隊列Flush,立即執行回流;
10、設置style屬性;
~~~
var s = document.body.style;
s.padding = "2px"; // 回流+重繪
s.border = "1px solid red"; // 再一次 回流+重繪
s.color = "blue"; // 再一次重繪
s.backgroundColor = "#ccc"; // 再一次 重繪
s.fontSize = "14px"; // 再一次 回流+重繪
// 添加node,再一次 回流+重繪
document.body.appendChild(document.createTextNode('abc!'));
~~~
## 回流的原因
瀏覽器在實現回流時,會遞歸地處理frame。 每個frame的回流都有一個原因, 這個原因會隨著frame逐級向下傳遞(傳遞過程中可能會改變)。 回流的原因決定了當前frame的回流行為,有這樣5種原因:
* 初始化(Initial)。DOM載入后的第一次回流,將會遍歷所有frame。
* 漸進(Incremental)。當一個frame發生漸進回流時,意味著它前面的元素都沒有變, 而是它里面的元素變了。這會引起自底向上的作用。
* 改變大小(Resize)。元素的容器邊界發生變化時,此時元素內部狀態沒變。 在計算自頂向下的布局約束的同時,可以復用內部狀態。
* 樣式改變(StyleChange)。整個frame樹都應得到遍歷。
* Dirty。當一個容器已經緩存了多個子元素的Incremental回流時,該容器出于Dirty的狀態。
前面四種原因的回流都是在Presentation Shell中立即調用的, 而最后一種回流只有Incremental回流已經到達目標frame時才進行。 (因為這時自底向上的影響才被計算出來,才能決定容器的圖形顯示)
# 重繪和回流和 Eventloop 有關
1. 當 Eventloop 執行完 Microtasks 后,會判斷`document`是否需要更新,因為瀏覽器是 60Hz 的刷新率,每 16.6ms 才會更新一次。
2. 然后判斷是否有`resize`或者`scroll`事件,有的話會去觸發事件,所以`resize`和`scroll`事件也是至少 16ms 才會觸發一次,并且自帶節流功能。
3. 判斷是否觸發了 media query
4. 更新動畫并且發送事件
5. 判斷是否有全屏操作事件
6. 執行`requestAnimationFrame`回調
7. 執行`IntersectionObserver`回調,該方法用于判斷元素是否可見,可以用于懶加載上,但是兼容性不好
8. 更新界面
9. 以上就是一幀中可能會做的事情。如果在一幀中有空閑時間,就會去執行`requestIdleCallback`回調。
以上內容來自于[HTML 文檔](https://link.juejin.im/?target=https%3A%2F%2Fhtml.spec.whatwg.org%2Fmultipage%2Fwebappapis.html%23event-loop-processing-model)。
# 如何減少回流、重繪
減少回流、重繪其實就是需要減少對render tree的操作(合并多次多DOM和樣式的修改),并減少對一些style信息的請求,盡量利用好瀏覽器的優化策略。具體方法有:
1. 直接改變className,如果動態改變樣式,則使用cssText(考慮沒有優化的瀏覽器)
~~~
// 不好的寫法
var changeDiv = document.getElementById('changeDiv');
changeDiv.style.color = '#093';
changeDiv.style.background = '#eee';
changeDiv.style.height = '200px';
// 比較好的寫法
div.changeDiv {
background: #eee;
color: #093;
height: 200px;
}
document.getElementById('changeDiv').className = 'changeDiv';
~~~
2. 讓要操作的元素進行”離線處理”,處理完后一起更新
a) 使用DocumentFragment進行緩存操作,引發一次回流和重繪;
b) 使用display:none技術(由于display屬性為none的元素不在渲染樹中,對隱藏的元素操作不會引發其他元素的重排。如果要對一個元素進行復雜的操作時,可以先隱藏它,操作完成后再顯示。這樣只在隱藏和顯示時觸發2次重排。);
c) 使用cloneNode(true or false) 和 replaceChild 技術,引發一次回流和重繪;
d) 將需要多次重排的元素,position屬性設為absolute或fixed;
3. 不要經常訪問會引起瀏覽器flush隊列的屬性,如果你確實要訪問,利用緩存
~~~
// 不好的寫法
for(循環) {
el.style.left = el.offsetLeft + 5 + "px";
el.style.top = el.offsetTop + 5 + "px";
}
// 比較好的寫法
var left = el.offsetLeft,
top = el.offsetTop,
s = el.style;
for (循環) {
left += 10;
top += 10;
s.left = left + "px";
s.top = top + "px";
}
~~~
4. 讓元素脫離動畫流,減少回流的Render Tree的規模
~~~
$("#block1").animate({left:50});
$("#block2").animate({marginLeft:50});
~~~
5. 在內存中多次操作節點,完成后再添加到文檔中去。例如要異步獲取表格數據,渲染到頁面。可以先取得數據后在內存中構建整個表格的html片段,再一次性添加到文檔中去,而不是循環添加每一行。
7. 不要用tables布局的另一個原因就是tables中某個元素一旦觸發reflow就會導致table里所有的其它元素reflow。在適合用table的場合,可以設置table-layout為auto或fixed,這樣可以讓table一行一行的渲染,這種做法也是為了限制reflow的影響范圍。
參考資料
* [重繪,回流和合成,了解基本瀏覽器繪制幫你優化頁面性能](https://zhuanlan.zhihu.com/p/23428399)
* [全新Chrome Devtool Performance使用指南](https://zhuanlan.zhihu.com/p/29879682)
* [頁面重繪和回流以及優化](http://www.css88.com/archives/4996)
- 第一部分 HTML
- meta
- meta標簽
- HTML5
- 2.1 語義
- 2.2 通信
- 2.3 離線&存儲
- 2.4 多媒體
- 2.5 3D,圖像&效果
- 2.6 性能&集成
- 2.7 設備訪問
- SEO
- Canvas
- 壓縮圖片
- 制作圓角矩形
- 全局屬性
- 第二部分 CSS
- CSS原理
- 層疊上下文(stacking context)
- 外邊距合并
- 塊狀格式化上下文(BFC)
- 盒模型
- important
- 樣式繼承
- 層疊
- 屬性值處理流程
- 分辨率
- 視口
- CSS API
- grid(未完成)
- flex
- 選擇器
- 3D
- Matrix
- AT規則
- line-height 和 vertical-align
- CSS技術
- 居中
- 響應式布局
- 兼容性
- 移動端適配方案
- CSS應用
- CSS Modules(未完成)
- 分層
- 面向對象CSS(未完成)
- 布局
- 三列布局
- 單列等寬,其他多列自適應均勻
- 多列等高
- 圣杯布局
- 雙飛翼布局
- 瀑布流
- 1px問題
- 適配iPhoneX
- 橫屏適配
- 圖片模糊問題
- stylelint
- 第三部分 JavaScript
- JavaScript原理
- 內存空間
- 作用域
- 執行上下文棧
- 變量對象
- 作用域鏈
- this
- 類型轉換
- 閉包(未完成)
- 原型、面向對象
- class和extend
- 繼承
- new
- DOM
- Event Loop
- 垃圾回收機制
- 內存泄漏
- 數值存儲
- 連等賦值
- 基本類型
- 堆棧溢出
- JavaScriptAPI
- document.referrer
- Promise(未完成)
- Object.create
- 遍歷對象屬性
- 寬度、高度
- performance
- 位運算
- tostring( ) 與 valueOf( )方法
- JavaScript技術
- 錯誤
- 異常處理
- 存儲
- Cookie與Session
- ES6(未完成)
- Babel轉碼
- let和const命令
- 變量的解構賦值
- 字符串的擴展
- 正則的擴展
- 數值的擴展
- 數組的擴展
- 函數的擴展
- 對象的擴展
- Symbol
- Set 和 Map 數據結構
- proxy
- Reflect
- module
- AJAX
- ES5
- 嚴格模式
- JSON
- 數組方法
- 對象方法
- 函數方法
- 服務端推送(未完成)
- JavaScript應用
- 復雜判斷
- 3D 全景圖
- 重載
- 上傳(未完成)
- 上傳方式
- 文件格式
- 渲染大量數據
- 圖片裁剪
- 斐波那契數列
- 編碼
- 數組去重
- 淺拷貝、深拷貝
- instanceof
- 模擬 new
- 防抖
- 節流
- 數組扁平化
- sleep函數
- 模擬bind
- 柯里化
- 零碎知識點
- 第四部分 進階
- 計算機原理
- 數據結構(未完成)
- 算法(未完成)
- 排序算法
- 冒泡排序
- 選擇排序
- 插入排序
- 快速排序
- 搜索算法
- 動態規劃
- 二叉樹
- 瀏覽器
- 瀏覽器結構
- 瀏覽器工作原理
- HTML解析
- CSS解析
- 渲染樹構建
- 布局(Layout)
- 渲染
- 瀏覽器輸入 URL 后發生了什么
- 跨域
- 緩存機制
- reflow(回流)和repaint(重繪)
- 渲染層合并
- 編譯(未完成)
- Babel
- 設計模式(未完成)
- 函數式編程(未完成)
- 正則表達式(未完成)
- 性能
- 性能分析
- 性能指標
- 首屏加載
- 優化
- 瀏覽器層面
- HTTP層面
- 代碼層面
- 構建層面
- 移動端首屏優化
- 服務器層面
- bigpipe
- 構建工具
- Gulp
- webpack
- Webpack概念
- Webpack工具
- Webpack優化
- Webpack原理
- 實現loader
- 實現plugin
- tapable
- Webpack打包后代碼
- rollup.js
- parcel
- 模塊化
- ESM
- 安全
- XSS
- CSRF
- 點擊劫持
- 中間人攻擊
- 密碼存儲
- 測試(未完成)
- 單元測試
- E2E測試
- 框架測試
- 樣式回歸測試
- 異步測試
- 自動化測試
- PWA
- PWA官網
- web app manifest
- service worker
- app install banners
- 調試PWA
- PWA教程
- 框架
- MVVM原理
- Vue
- Vue 餓了么整理
- 樣式
- 技巧
- Vue音樂播放器
- Vue源碼
- Virtual Dom
- computed原理
- 數組綁定原理
- 雙向綁定
- nextTick
- keep-alive
- 導航守衛
- 組件通信
- React
- Diff 算法
- Fiber 原理
- batchUpdate
- React 生命周期
- Redux
- 動畫(未完成)
- 異常監控、收集(未完成)
- 數據采集
- Sentry
- 貝塞爾曲線
- 視頻
- 服務端渲染
- 服務端渲染的利與弊
- Vue SSR
- React SSR
- 客戶端
- 離線包
- 第五部分 網絡
- 五層協議
- TCP
- UDP
- HTTP
- 方法
- 首部
- 狀態碼
- 持久連接
- TLS
- content-type
- Redirect
- CSP
- 請求流程
- HTTP/2 及 HTTP/3
- CDN
- DNS
- HTTPDNS
- 第六部分 服務端
- Linux
- Linux命令
- 權限
- XAMPP
- Node.js
- 安裝
- Node模塊化
- 設置環境變量
- Node的event loop
- 進程
- 全局對象
- 異步IO與事件驅動
- 文件系統
- Node錯誤處理
- koa
- koa-compose
- koa-router
- Nginx
- Nginx配置文件
- 代理服務
- 負載均衡
- 獲取用戶IP
- 解決跨域
- 適配PC與移動環境
- 簡單的訪問限制
- 頁面內容修改
- 圖片處理
- 合并請求
- PM2
- MongoDB
- MySQL
- 常用MySql命令
- 自動化(未完成)
- docker
- 創建CLI
- 持續集成
- 持續交付
- 持續部署
- Jenkins
- 部署與發布
- 遠程登錄服務器
- 增強服務器安全等級
- 搭建 Nodejs 生產環境
- 配置 Nginx 實現反向代理
- 管理域名解析
- 配置 PM2 一鍵部署
- 發布上線
- 部署HTTPS
- Node 應用
- 爬蟲(未完成)
- 例子
- 反爬蟲
- 中間件
- body-parser
- connect-redis
- cookie-parser
- cors
- csurf
- express-session
- helmet
- ioredis
- log4js(未完成)
- uuid
- errorhandler
- nodeclub源碼
- app.js
- config.js
- 消息隊列
- RPC
- 性能優化
- 第七部分 總結
- Web服務器
- 目錄結構
- 依賴
- 功能
- 代碼片段
- 整理
- 知識清單、博客
- 項目、組件、庫
- Node代碼
- 面試必考
- 91算法
- 第八部分 工作代碼總結
- 樣式代碼
- 框架代碼
- 組件代碼
- 功能代碼
- 通用代碼