详谈浏览器渲染过程

142 阅读6分钟

前言

这两天看了很多关于浏览器渲染过程的文章,感觉都不是特别全面。有的没有讲到合成层,有的讲了合成层的又没有前面的步骤,所以决定写一篇文章,按照自己的理解归纳总结一下,有不对的地方欢迎大家到评论区一起讨论~

浏览器大致渲染过程

  1. 解析HTML生成DOM树
  2. 解析CSS生成CSSOM树
  3. DOM树和CSSOM树结合,生成Render树
  4. Render树根据特殊属性生成Layer树
  5. 进行绘制
  6. 进行合成

1. 解析HTML生成DOM

将HTML解析为DOM树,共分为以下几个步骤:

image.png (1)浏览器从磁盘或网络读取HTML的原始字节(字节数据),并根据文件的编码将他们转换成我们能读懂的字符串。

(2)接着讲字符串转换成Token,Token会识别出当前Token是“开始标签”、“结束标签”或“文本”等信息。

(3)接着根据”开始标签“和”结束标签“转化成Node节点,构建DOM。构建DOM的过程中,是一边解析一边生成节点对象的。换句话说,每个Token被生成后,会立马消耗这个Token来创建出节点对象。注意:带有”结束标签“标识的Token不会创建节点对象

2. 解析CSS生成CSSOM

根据DOM树浏览器就可以知道页面有什么内容,但是浏览器还需要知道这些内容该如何展示,所以需要根据CSS样式文件来构建CSSOM。构建CSSOM的过程与构建DOM的过程非常相似:

image.png 在构建CSSOM树的过程中,浏览器会确定每个节点的样式是什么,这一过程是很消耗资源的,因为一个节点的样式我们可以自行设置,也可以通过继承获得。所以CSS匹配HTML元素是一个相当复杂和有性能问题的过程。

因此DOM树要尽可能的小,CSS尽量使用id和class来匹配,并且样式千万不要过多的层叠。

3. 生成Render树

在讲解生成Render树之前,我们先来了解一些有关绘制的概念。

位图

当绘制一个图片时,需要将这个图片表示为计算机能够理解的数据结构——位图:用一个二维数组,数组中的每一个元素记录这个图片中每一个像素的具体颜色。

image.png

所以浏览器可以使用位图来记录他在某个区域所绘制的内容,绘制的过程就是根据位图所记录的颜色来填充像素。

纹理

纹理其实就是GPU(图像处理器)中的位图。

光栅化

image.png

在纹理里填充像素不是简单地遍历位图中的每个元素,然后填写元素的颜色,而是先进行光栅化。光栅化的本质是坐标变换、几何离散化、然后再填充像素。

现在的光栅化不再是对整个图像进行光栅化,而是将图像分块(tile)后,再对每个tile单独进行光栅化。光栅化完成后将像素填充进纹理,再将纹理上传至GPU。

Render Object

到现在这个阶段,我们已经有了DOM树和CSSOM树,但这两个树只是供给JS/HTML/CSS使用的,并不能直接拿来在浏览器页面或位图中进行显示,因此有了Render树。通过将DOM树和CSSOM树结合,生成Render树。Render树中的每一个Render Object与DOM树中的节点一一对应。Render Object上提供了将对应DOM节点绘制进位图的方法,它负责绘制这个DOM节点的可见内容,如:文字、背景、边框等。

4. 生成Layer树

然而,光有Render Object的内容还不能完成浏览器的绘制,因为它不能决定元素之间的覆盖关系,如position定位、z-index层序等。因此浏览器还有个层叠上下文Render Layer来决定元素之间的层叠关系。

Render Layer

Render Layer的出现不仅仅是简单的因为元素之间的层叠关系,还有比如opacity小于1等需要先回执号内容,再对内容作出一些统一处理的css效果。

总之,有层叠、半透明等情况的元素,就要从Render Object提升为Render Layer。没有这些情况,不需要提升为Render Layer的Render Object就从属于其父元素中最近的那个Render Layer。此时Render树就变成了Layer树,每个Render Layer包含了属于它的Layer和Render Object。

Layer树决定了网页绘制的层次顺序,而从属于Render Layer的Render Object决定了这个Layer的内容,所有的Render Layer和Render Object决定了网页在屏幕上最终呈现出来的内容。

到这里位置,浏览器已经可以完成绘制过程,但为了优化性能,还有Graphics Layer,我们后面会提到。

5. 绘制

在这个阶段,浏览器根据Layer树进行页面的绘制

6. 合成

因为浏览器中经常会有动画、video、canvas等的css变化,这意味着当页面中有这些元素时,因为这些元素经常变动,所以位图也会经常变动。在每秒60帧的变动里,每一次变动都要触发一次重排或重绘,这是非常大的性能消耗。因此就有了Graphics Layer和Graphics Context。

Graphics Layer和Graphics Context

Graphics Layer,又称为Compositing Layer合成层。

某些具有CSS3的3D transform的元素、在opacity、transform属性上有动画的元素、硬件加速的canvas和video元素等,会在第4步从Render Object提升为Render Layer,接着又会提升为Graphics Layer,每个不提升的Render Layer会从属于他父元素中最近的Graphics Layer。

每个Graphics Layer都有一个Graphics Context,Graphics Context会为该Layer开辟一个位图。Graphics Layer负责将自己的Render Layer及自带的Render Object绘制进位图里,然后将该位图作为纹理交给GPU进行相应的处理。

composite合成

现在GPU需要对多层纹理进行合成,同时GPU在纹理合成时对每一层的纹理都可以指定不同的合成参数,从而实现对纹理进行transform、opacity等不同的操作之后再进行合成。而对于这个过程GPU是底层硬件加速的,性能很好。最终纹理合成后再draw到屏幕上。

上述分层后合并的过程可以用一张图来描述:

image.png

影响提升至合成层的因素有:

  • 3D transforms: translate3d, translateZ 等;

  • video, canvas, iframe 等元素;

  • 通过 Element.animate() 实现的 opacity 动画转换;

  • 通过 СSS 动画实现的 opacity 动画转换;

  • position: fixed;

  • will-change;

  • filter;

  • 有合成层后代同时本身 overflow 不为 visible(如果本身是因为明确的定位因素产生的 SelfPaintingLayer,则需要 z-index 不为 auto)

  • ...

到这渲染过程就结束了。

整体渲染过程如下图所示:

image.png

上图出自The Anatomy of a Frame

参考文章:

浏览器渲染详细过程:重绘、重排和 composite 只是冰山一角

浏览器渲染原理流程