浏览器渲染过程
渲染过程如下图:

解析 HTML
解析 HTML 得到 DOM 树;解析 CSS 得到 CSSOM 树
解析前:
启动一个预解析的线程,率先下载 HTML 中的外部 CSS 文件和外部的 JS 文件,这样可以提高解析效率
解析时:
解析到 link 位置(遇到 CSS 代码),此时外部的 CSS 文件还没有下载解析好,主线程不会等待,继续解析后续的 HTML
CSS 不会阻塞 HTML 解析,就是因为下载和解析 CSS 的工作是由预解析线程和网络线程进行的
解析到 script 位置(遇到 JS 代码),就会停止解析 HTML,转而等待 JS 文件下载好,并将全局代码解析执行完成后,才能继续解析 HTML
JS 阻塞 HTML 解析的根本原因:JS 代码执行过程中可能会修改当前的 DOM 树,所以 DOM 树的生成必须暂停
样式计算
主线程会遍历得到的 DOM 树,依次为树中的每个节点计算出它最终的样式,称之为 Computed Style
在这个过程中,很多预设值会变成绝对值,比如 red 会变成 rgb(255,0,0);相对单位会变成绝对单位,比如 em 会变成 px
结果:得到一棵带有样式的 DOM 树
布局
依次遍历 DOM 树的每一个节点,计算每个节点的几何信息。例如节点的宽高、相对包含块的位置
布局完成会得到布局树。大部分时候,DOM 树和布局树并非一一对应,比如 display:none 的节点没有几何信息,就不会生成到布局树。又或者使用了伪元素选择器,虽然 DOM 树中不存在这些伪元素节点,但它们拥有几何信息,所以会生成到布局树中。还有就是匿名行盒、匿名块盒等等都会导致 DOM 树和布局树无法一一对应
分层
主线程会使用一套复杂的策略对整个布局树中进行分层。将来某一个层改变后,仅会对该层进行后续处理,这样就能提升效率
滚动条、堆叠上下文、transform、opacity 等样式都会或多或少的影响分层结果,也可以通过 will-change 属性更大程度的影响分层结果
绘制
主线程会为每个层单独产生绘制指令集,用于描述这一层的内容该如何画出来。绘制完成后,主线程将每个图层的绘制信息提交给合成线程,剩余工作将由合成线程完成
分块
合成线程首先对每个图层进行分块,将其划分为更多的小区域,它会从线程池中拿取多个线程来完成分块工作
光栅化
目的:将每个块变成位图
合成线程会将块信息交给 GPU 进程,以极高的速度完成光栅化。GPU 进程会开启多个线程来完成光栅化,并且优先处理靠近视口区域的块
结果:得到一块一块的位图
画
合成线程拿到每个层、每个块的位图后,生成一个个「指引(quad)」信息。指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转、缩放等变形。然后合成线程会把 quad 提交给 GPU 进程,由 GPU 进程产生系统调用,提交给 GPU 硬件,完成最终的屏幕成像
transform 效率高的根本原因
transform 既不会影响布局也不会影响绘制指令,它影响的只是渲染流程的最后一个 draw 阶段。而 draw 在合成线程中,所以 transform 的变化几乎不会影响渲染主线程