浏览器渲染流程
由于渲染机制过于复杂,所以渲染模块在执行过程中会被划分为很多子阶段,输入的 HTML 经过这些子阶段,最后输出像素。我们把这样的一个处理流程叫做渲染流水线
流水线可分为如下几个子阶段:构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成
结合上图,一个完整的渲染流程大致可总结为如下:
- 渲染进程将html渲染成DOM树
- 渲染引擎将css样式表转化成styleSheets,计算出DOM节点的样式
- 创建布局树、并计算元素的布局信息
- 对布局树进行分层,创建分层树
- 为每个图层生成绘制列表,并将其提交到合成线程
- 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图
- 合成线程发送绘制图块命令 DrawQuad 给浏览器进程
- 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上
重排 重绘和合成
结合以上渲染流程的步骤,再来看一下浏览器中的重排 重绘和合成
重排:更新了元素的几何属性 比如修改高度
从上图可以看出,如果你通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。
什么情况下会触发重排
- 改变窗口大小
- 改变字体大小
- 增加或删除 DOM 元素
- 改变元素位置
- 修改元素尺寸
重绘:更新元素的绘制属性
从图中可以看出,如果修改了元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。
什么情况下会触发重绘
- 修改元素背景色
- 修改文本颜色
- 添加阴影或轮廓
合成:直接合成阶段
在上图中,我们使用了 CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。
重排、重绘怎么优化
- 将多次改变样式属性的操作合并成一次操作,减少 DOM 访问。
- 如果要批量添加 DOM,可以先让元素脱离文档流,操作完后再带入文档流,这样只会触发一次重排。(fragment 元素的应用)
- 将需要多次重排的元素,position 属性设为 absolute 或 fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位。
- 由于 display 属性为 none 的元素不在渲染树中,对隐藏的元素操作不会引发其他元素的重排。如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示。这样只在隐藏和显示时触发两次重排。
- 在内存中多次操作节点,完成后再添加到文档中去。例如要异步获取表格数据,渲染到页面。可以先取得数据后在内存中构建整个表格的 html 片段,再一次性添加到文档中去,而不是循环添加每一行。
- 使用 class 操作样式,而不是频繁操作 style
- 避免使用 table 布局
- Debounce window resize 事件
- will-change: transform 做优化
相关扩展
JS和CSS都有可能会阻塞DOM解析
参考文档:https://blog.csdn.net/TianXuab/article/details/134958080