本文总结从输入URL到页面显示的第二部分内容:渲染(从拿到HTML资源到显示的过程)
主要过程
- 构建DOM树
- 样式计算,分成三步
- 把 CSS 转换为浏览器能够理解的结构–styleSheets
- 转换样式表中的属性值,使其标准化
- 计算出 DOM 树中每个节点的具体样式(继承和层叠规则)
- 布局:计算出DOM树中可见元素的几何位置。在这个阶段完成两个任务
- 创建布局树:额外构建一颗值包含可见元素的布局树
- 布局计算
- 对布局树进行分层,并生成分层树:并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。通常满足下面两点中任意一点的元素就可以被提升为单独的一个图层。
- 第一点,拥有层叠上下文属性的元素会被提升为单独的一层。(position:fixed;z-index:2;filter:blue(5px);opacity:0.5等等)
- 第二点,需要剪裁(clip)的地方也会被创建为图层。(滚动条等)
- 为每个图层生成绘制列表,并将其提交到合成线程。
- 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
- 合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池。通常,栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。
- 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
- 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。
补充说明
- 有了上面介绍渲染流水线的基础,我们再来看看三个和渲染流水线相关的概念——“重排”“重绘”和“合成”。
- 更新了元素的几何属性(重排):如果你通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。
- 更新元素的绘制属性(重绘):通过 JavaScript 更改某些元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。
- 直接合成阶段:如果更改一个既不要布局也不要绘制的属性,渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成。如使用了 CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。
总结: 渲染过程
- 构建dom树 根据html文件中标签结构生成dom树 输入: html文件 经过html解析器 输出:树状结构的dom (document可以查看)
- 样式计算 输入: css 输出styleSheets
- 将css(包括外联css.''中的css和元素style属性的行内css)转化为浏览器可以理解的styleSheets (document.styleSheets可以查看)
- 标准化 统一和标准化表示rem px % 这种不同的单位
- 计算出 DOM 树中每个节点的具体样式,涉及到 CSS 的继承规则和层叠规则
- 布局 输入 dom树和styleSheets 输出: layout布局树
- 创建布局树: 根据dom树和styleSheets输出一颗只包含可见元素的dom树
- 布局计算 : 计算完全可见dom树上每个节点的位置
- 分层(输入:layout布局树 输出 : layerTree) 针对一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree).通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。
- 生成图层绘制指令 (输入:layerTree 输出: 绘制指令列表)
- raster栅格化操作(输入: 绘制指令列表 输出:位图): 所谓栅格化是指将图块转换为位图,而图块是栅格化执行的最小单位。
绘制列表只是用来记录绘制顺序和绘制指令的列表,实际上绘制操作是由渲染引擎中的合成线程来完成的。当图层的绘制列表准备好之后,合成线程会将图层划分为图块。
然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的。通常栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。
(渲染进程维护栅格化线程池,在GPU进程中执行栅格化的具体操作,并在GPU进程中存储栅格化之后的位图)
- 合成和显示 :
一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。(渲染进程->浏览器进程)
浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上 (浏览器向GPU进程拿位图来显示)
参考文章:06 | 渲染流程(下):HTML、CSS和JavaScript,是如何变成页面的?