HTML、CSS和JavaScript是怎么变成页面的

65 阅读4分钟

浏览器渲染流程

由于渲染机制过于复杂,所以渲染模块在执行过程中会被划分为很多子阶段,输入的 HTML 经过这些子阶段,最后输出像素。我们把这样的一个处理流程叫做渲染流水线

流水线可分为如下几个子阶段:构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成

image.png

结合上图,一个完整的渲染流程大致可总结为如下:

  1. 渲染进程将html渲染成DOM树
  2. 渲染引擎将css样式表转化成styleSheets,计算出DOM节点的样式
  3. 创建布局树、并计算元素的布局信息
  4. 对布局树进行分层,创建分层树
  5. 为每个图层生成绘制列表,并将其提交到合成线程
  6. 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图
  7. 合成线程发送绘制图块命令 DrawQuad 给浏览器进程
  8. 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上

重排 重绘合成

结合以上渲染流程的步骤,再来看一下浏览器中的重排 重绘合成

重排:更新了元素的几何属性 比如修改高度

image.png

从上图可以看出,如果你通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。

什么情况下会触发重排

  1. 改变窗口大小
  2. 改变字体大小
  3. 增加或删除 DOM 元素
  4. 改变元素位置
  5. 修改元素尺寸

重绘:更新元素的绘制属性

image.png

从图中可以看出,如果修改了元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。

什么情况下会触发重绘

  1. 修改元素背景色
  2. 修改文本颜色
  3. 添加阴影或轮廓

合成:直接合成阶段

image.png 在上图中,我们使用了 CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。

重排、重绘怎么优化

  1. 将多次改变样式属性的操作合并成一次操作,减少 DOM 访问。
  2. 如果要批量添加 DOM,可以先让元素脱离文档流,操作完后再带入文档流,这样只会触发一次重排。(fragment 元素的应用)
  3. 将需要多次重排的元素,position 属性设为 absolute 或 fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位。
  4. 由于 display 属性为 none 的元素不在渲染树中,对隐藏的元素操作不会引发其他元素的重排。如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示。这样只在隐藏和显示时触发两次重排。
  5. 在内存中多次操作节点,完成后再添加到文档中去。例如要异步获取表格数据,渲染到页面。可以先取得数据后在内存中构建整个表格的 html 片段,再一次性添加到文档中去,而不是循环添加每一行。
  6. 使用 class 操作样式,而不是频繁操作 style
  7. 避免使用 table 布局
  8. Debounce window resize 事件
  9. will-change: transform 做优化

相关扩展

JS和CSS都有可能会阻塞DOM解析

参考文档:https://blog.csdn.net/TianXuab/article/details/134958080

参考文档:https://vue3js.cn/interview/css/layout_painting.html#%E4%B8%89%E3%80%81%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91

参考文档:浏览器工作原理与实践