重排和重绘

# 页面生成的过程
- HTML 被 HTML 解析器解析成 DOM 树
- CSS 被 CSS 解析器解析成 CSSOM 树
- 结合 DOM 树和 CSSOM 树生成一颗渲染树 (Render Tree),此过程为 Attachment
- 生成布局(flow),浏览器在屏幕上画出渲染树中的所有节点
- 将布局绘制(paint)在屏幕上,显示出整个页面
# 重排比重绘大
- 重绘不一定导致重排,但重排一定导致重绘
# 重排(reflow)
重排也叫回流,重新生成布局,重新排列元素
# 哪些情况会发生重排
- 页面初始渲染
- 添加/删除可见的 DOM 元素
- 改变元素位置
- 改变元素尺寸,如边距、填充、边框、宽高等
- 改变元素内容,如文字数量,图片大小等
- 改变元素字体大小
- 改变浏览器窗口尺寸,如 resize 事件发生时
- 激活 CSS 伪类,如
:hover - 设置 style 属性的值,因通过设置 style 属性改变节点样式,每次设置都会触发一次 reflow
- 查询某些属性或调用某些计算方法:
offsetWidth,offsetHeight等,调用getComputedStyle方法
| 常见引起重排属性和方法 | -- | -- | -- |
|---|---|---|---|
| width | height | margin | padding |
| display | border-width | border | position |
| overflow | font-size | vertical-align | min-height |
| clientWidth | clientHeight | clientTop | clientLeft |
| offsetWidth | offsetHeight | offsetTop | offsetLeft |
| scrollWidth | scrollHeight | scrollTop | scrollLeft |
| scrollIntoView() | scrollTo() | getComputedStyle() | |
| getBoundingClientRect() | scrollIntoViewIfNeeded() |
# 重排影响的范围
由于浏览器渲染界面是基于流式布局模型,所以触发重排时会对周围的 DOM 重新排列
- 全局范围:从根节点 html 开始对整个渲染树进行重新布局
- 局部范围:对渲染树的某部分或某一个渲染你对象进行重新布局
# 重绘(Repaints)
当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫重绘
| 常见的引起重绘的属性 | -- | -- | -- |
|---|---|---|---|
| color | border-style | visibility | background |
| text-decoration | background-image | background-position | background-repeat |
| outline-color | outline | outline-style | border-radius |
| outline-width | box-shadow | background-size |
# 重排优化建议
减少重排范围
- 尽可能在低层级的 DOM 节点上,不要通过父元素去影响子元素
- 不要使用 table 布局,可能很小的改动造成整个 table 的重新布局,不得已使用 table,可设置
table-layout:auto或table-layout:fixed使 table 一行一行渲染,限制 reflow 的影响范围
减少重排次数
样式集中改变
- 样式统一在
cssText变量中编辑 - 更改类名而不是直接修改样式
// // bad var left = 10; var top = 10; el.style.left = left + "px"; el.style.top = top + "px"; // 当top和left的值是动态计算而成时... // better el.style.cssText += "; left: " + left + "px; top: " + top + "px;"; // better el.className += " className";1
2
3
4
5
6
7
8
9
10
11
12
13
- 样式统一在
分离读写操作
- DOM 的多个读操作(或多个写操作),应该放在一起。
// bad 强制刷新 触发四次重排+重绘 div.style.left = div.offsetLeft + 1 + "px"; div.style.top = div.offsetTop + 1 + "px"; div.style.right = div.offsetRight + 1 + "px"; div.style.bottom = div.offsetBottom + 1 + "px"; // good 缓存布局信息 相当于读写分离 触发一次重排+重绘 var curLeft = div.offsetLeft; var curTop = div.offsetTop; var curRight = div.offsetRight; var curBottom = div.offsetBottom; div.style.left = curLeft + 1 + "px"; div.style.top = curTop + 1 + "px"; div.style.right = curRight + 1 + "px"; div.style.bottom = curBottom + 1 + "px";1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
将 DOM 离线
- 使用
dispaly:none - 通过DocumentFragment (opens new window)创建一个
dom碎片,在此上面批量操作dom,操作完成后,再添加到文档中,这样只会触发一次重排 - 复制节点,在副本上工作,然后替换他
- 使用
使用 absolute 或 fixed 脱离文档流
- 使用绝对定位会使该元素独立成为渲染树中
body的一个子元素,一些其他在这个区域的节点可能需要重绘,但不需要重排
- 使用绝对定位会使该元素独立成为渲染树中
优化动画
- 把动画效果应用到
position:absolute/fixed的元素上 - 启用 CPU 加速:Canvas2D/布局合成、transitions、transforms、WebGL 和 video
- 把动画效果应用到
# 如何在浏览器中查看页面渲染时间
打开开发者工具:点击 Performance 左侧有个小圆点 点击刷新页面会录制整个页面加载出来 时间的分配情况。如图
- 蓝色:网络通信和 HTML 解析
- 黄色:js 执行
- 紫色:样式计算和布局,即重排
- 绿色:重绘
点击 Event Log:单独勾选 Loading 项会显示 html 和 css 加载时间。如下
上次更新: 2021/04/22, 00:08:06