在为什么打开一个页面,会有4个进程?文章中提及了浏览器的几个主要进程,这回来说明一下渲染流程
渲染进程的核心工作是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,由于渲染机制过于复杂,所以渲染模块在执行的过程中会被划分成很多子阶段,通过输入 HTML 经过这些子阶段最后输出像素,我们把这样一个处理流程叫做渲染流水线
按照渲染时间的顺序,流水线可以分为以下几个子阶段:
- 构建 DOM 树
- 样式计算
- 布局
- 分层
- 绘制
- 分块
- 光栅化
- 合成
构建 DOM 树
在渲染引擎内部,有个叫 HTML解析器(HTMLParser)的模块,负责将 HTML 字节流转换成 DOM 结构
通过分词器将字节流转换为Token;Token分为Tag Token和文本Token。Tag Token又可分为StartTag和EndTag,对应着标签的开和闭,eg:
、将Token解析为DOM节点(图中的Node),并将DOM节点添加到DOM树中。HTML解析器维护了一个Token栈结构,用来计算节点之间的父子关系。类似于用栈来匹配带括括号的表达式”3*(7+(4÷2))”,不再详细展开
样式计算
样式计算的目的是为了计算出DOM节点中每个元素的具体样式,这一阶段大致可分三步来完成:
- 把CSS转换为浏览器能够理解的结构
渲染引擎无法直接理解CSS文件的内容,所以需要将其解析成渲染引擎能够理解的结构,即styleSheets。在浏览器的控制台输入document.styleSheets就可以查看其结构
CSSOM(CSS Object Model)是一组允许用JavaScript操纵CSS的API。CSSOM有两个作用- 提供给JavaScript操作样式表的能力
- 为布局树的合成提供基础的样式信息
- 转换样式表中的属性值,使其标准化
有一些CSS属性,像font-size: 2em、coler: blue、font-weight: bold,不容易被渲染引擎理解,所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化 - 计算出DOM树中每个节点的具体样式
CSS的继承规则和层叠规则
布局
有了 DOM 树和 DOM 树中元素的样式,但还不能显示页面,因为还缺少了 DOM 元素的几何位置信息,计算出 DOM 树中可见元素的几何位置,这个过程叫布局
布局阶段需要完成的两个任务:
- 创建布局树
布局树的基本结构就是复制 DOM 树的结构,循环遍历 DOM 树中所有可见节点,并把这些节点加到布局树中,而不可见的节点会被忽略掉(不可见的元素例如标签下的全部内容,被设为display: none的元素) - 布局计算
分层
页面中有很多复杂的效果,如3D变换、页面滚动、或者使用z-index做z轴排序等,为了更方便的实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一颗对应的图层树
通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层
当满足以下条件时,元素就可以被提升为一个单独的图层:
拥有层叠上下文属性的元素会被提升为单独的一层
从图中可以看出,明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素等,都拥有层叠上下文属性
需要剪裁的地方也会被创建为图层
绘制
构建完图层树之后,渲染引擎会对图层树中的每个图层进行绘制,把图层的绘制拆分成一个个小的绘制指令,如先画个矩形、再画个边框、画个背景,这些绘制指令组成一个绘制列表
光栅化
光栅化就是按照绘制列表中的指令生成图片,每一个图层都对应一张图片
绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的,当图层的绘制列表准备好之后,主线程会把绘制列表提交给合成线程
结合下图来看下渲染主线程和合成线程之间的关系:
当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程,那么接下来合成线程是怎么工作的呢?
一个页面通常很大,但用户只能看到其中的一部分,这部分叫做视口,看不到的部分是没有必要绘制出来的,因此,合成线程会把图层分为图块
合成线程会按照视口附近的图块优先生成位图,实际生成位图的操作由栅格化来执行,所谓栅格化,是指将图块转换为位图,而图块是栅格化执行的最小单位
渲染进程维护了一个栅格化的线程池,所有图块栅格化都是在线程池内执行
通常,栅格化过程都会使用GPU来加速生成,使用GPU生成位图的过程叫做快速栅格化,或者GPU栅格化,生成的位图会被保存在GPU中,
合成与显示
在光栅化完成之后,每个图层都会对应一张图片,合成线程会将这些图片合成”一张“图片
一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上
合成操作是在合成线程上完成,这也就意味着在执行合成操作时,是不会影响到主线程执行的。这也是为什么经常主线程卡住了,但是CSS动画依然能执行的原因
Q:显示器是怎么显示图像的?
A:显示器有固定的刷新频率,通常是60HZ,也就是每秒更新60张图片,更新的图片都来自于显卡中一个叫前缓冲区的地方,显示器会固定读取60次前缓冲区的图像,并将读取到的图像更新到屏幕上
Q:显卡做什么呢?
A:显卡的职责就是合成新图像,并将图像保存到后缓冲区中,一旦显卡把合成的图像写到后缓冲区,系统就会让后缓冲区和前缓冲区互换,这样