Skip to content

渲染就是从 HTML 文档到像素的过程,主要由渲染进程的渲染主线程和合成线程实现。

网络线程通过地址栏的 url 获取对应的 HTML 文档,并生成渲染任务加入渲染主线程的消息队列。 通过事件循环,渲染主线程取出渲染任务并进行处理,即开始执行渲染步骤。

解析 HTML

将 HTML 文档解析为 DOM 树和 CSSOM 树,根节点的接口分别为 documentdocument.styleSheets

预解析线程

和渲染主线程几乎同时启动,负责快速浏览 HTML 文档,查找外链 CSS 和 JS 文件。 找到后,通知网络线程提前下载,并对 CSS 文件进行预解析,最后发送给渲染主线程。

  • 如果渲染主线程解析到外链 CSS 时文件还没下载完,则跳过。
  • 如果渲染主线程解析到外链 JS 时文件还没下载完,则等待文件下载,在执行 JS 完后才继续解析 HTML。这样设计的原因是 JS 可能会修改 DOM 树。

样式计算

通过 DOM 树和 CSSOM 树计算每个节点的最终样式,可以通过 getComputedStyle 获取。主要包含 2 个过程:

  • css 属性值计算
    • 层叠
    • 继承
    • 预设值变绝对值:red --> rgb(255, 0, 0)
    • 相对单位变绝对单位:em --> px
  • 视觉格式化模型
    • 盒模型
    • 包含块

布局

根据样式计算每个节点的尺寸和位置,生成布局树。DOM 树和布局树并不一一对应:

  • 如果一个节点样式存在 display: none,该节点及其子节点不会出现在布局树中
  • 伪元素节点不出现在 DOM 树种,而出现在布局树中
  • ...

clientHeightoffsetHeight 属于布局属性。

重排 reflow

reflow 的本质是重新计算布局树。当 JS 执行了影响布局树的操作后,则引发 reflow。

具体情况是,这些影响布局树的操作会包装为一个布局任务加入消息队列,等到 JS 全部执行完后再 reflow。 但是这样会导致获取节点布局属性时,无法获取 reflow 后的值,所以浏览器决定在获取布局属性时立即执行 reflow。

分层

分层是为了细化绘制单位,避免过度重绘导致的性能损耗。

  • 滚动条专门在一个层中
  • 堆叠上下文相关的 css 属性将影响分层

INFO

可以打开 Devtools 的 Layers 面板查看。

绘制

为每个层生成绘制指令集,这是渲染主线程的最后一个任务。

重绘 repaint

reflow 的本质是重新生成绘制指令。改动了可见样式后,则引发 repaint。

requestAnimationFrame 的回调将在下一次 repaint 前运行,因为回调可能更改节点。

分块

将每个层分为多个小区域,以便后续优先画出靠近视口区域的块。 渲染主线程将各层的指令集交给合成线程,合成线程会从线程池中获取多个线程完成分块工作。

光栅化

将每个块变成位图,可以使用 GPU 加速。 合成线程将每个块交给 GPU 进程,GPU 进程处理完后把位图返回给合成线程。

合成线程计算出每个位图在屏幕上的位置,然后交给 GPU 进程呈现。

INFO

css 的 transform 属性在这一步被解析,这就是 transform 动画性能表现优异的原因。