开启渲染
浏览器网络线程接收到html文档后,会生成一个渲染任务,并将其加入到渲染主线程的消息队列中,根据事件循环机制,渲染主线程从消息队列中取出任务,开启渲染流程。
渲染流程
渲染流程分为多个阶段:解析HTML,样式计算,layout,分层,绘制,分块,光栅化,draw。每个阶段都有明确的输入输出,上一个阶段的输出会成为下一阶段的输入。
解析HTML
为了提高解析效率,浏览器会开启一个预解析线程,提前下载HTML文件中的外部CSS和JS文件。
如果HTML解析过程中遇到link标签,主线程不会等待而是继续解析后面的HTML元素,因为CSS的解析与下载都在预解析线程中进行。
如果HTML解析过程中遇到script标签,主线程会停止解析HTML,转而等待JS下载完成,并将全局代码执行完成后,才会继续解析HTML,因为JS代码执行过程中可能修改当前的DOM树。
结果:DOM树和CSSOM树。
样式计算
主线程根据生成的DOM树,依次为树中每个节点计算它的最终样式,称为Computed Style。
在这一过程中许多预设值会变成绝对值,相对单位会变成绝对单位。
结果:带样式的DOM树。
layout
布局阶段会依次遍历DOM树的每一个节点,计算每个节点的几何信息。例如节点的宽高,相对包含块的位置。
大部分时候,DOM和layout树并非一一对应。
比如**display: none;**的元素没有几何信息,不会生成到布局树,伪元素节点拥有几何信息,会生成到布局树。(内容必须包含在行盒中,行盒和块盒不能相邻)
结果:layout树
分层
主线程采用一套复杂的策略对layout树进行分层。
分层的好处在于每一层之间互不影响,提高绘制效率。
滚动条、堆叠上下文、opacity、transform等样式多少都会影响分层结果,也可以通过will-change属性更大程度上影响分层结果。
绘制
主线程会为每一层产生一个单独的指令集,用来描述这一层应该如何绘制。
结果:图层绘制指令集
分块
完成绘制后,主线程将每个图层的绘制信息交给合成线程,剩下的工作由合成线程完成。
合成线程首先对每个图层进行分块,将其划分成更小的区域。
它将从线程池中拿取多个线程进行分块工作。
结果:图层分块信息
光栅化
合成线程将图层的分块信息提交给GPU进程,由GPU进程开启多个线程进行进行光栅化,优先处理靠近视口区域的块。
结果:块位图
draw
合成线程拿到每个图层,每个块的位图后,生成一个个quad信息。
quad会标识出每个位图对应的屏幕位置,考虑旋转、缩放等变形操作。(变形发生在合成线程,与渲染主线程无关,所以transform效率较高)
合成线程会把quad提交给GPU进程,由GPU进程产生系统调用,提交给GPU硬件,最终完成屏幕成像。