浏览器渲染流程

本文整理了渲染的整个过程细节介绍:HTML->DOM树、样式计算、布局、图层、图层绘制、光栅化、合成和显示,以及重排和重绘触发原理及优化。

正文

渲染流程

image

  • 渲染进程将HTML内容转换为能够读懂的DOM树结构。
  • 渲染引擎将CSS样式表转化为浏览器可以理解的styleSheets,计算出DOM节点的样式。
  • 创建布局树,并计算元素的布局信息。
  • 对布局树进行分层,并生成分层树。
  • 为每个图层生成绘制列表,并将其提交到合成线程。
  • 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
  • 合成线程发送绘制图块命令DrawQuad给浏览器进程。
  • 浏览器进程根据DrawQuad消息生成页面,并显示到显示器上

重排(回流)和重绘

  • 更新了元素的几何属性(重排reflow)
    image
    从上图可以看出,如果你通过JavaScript或者CSS修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。

  • 更新元素的绘制属性(重绘repaint)
    image
    从图中可以看出,如果修改了元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。

  • 3D变化直接合成阶段
    image
    在上图中,我们使用了CSS的transform来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。

优化-减少重排和重绘

  • 触发repaint reflow的操作尽量放在一起,比如改变dom高度和设置margin分开写,可能会出发两次重排
  • 通过虚拟dom层计算出操作总得差异,一起提交给浏览器。比如使用createdocumentfragment来汇总append的dom,来减少触发重排重绘次数。

具体细节的原理

样式计算生成CSSOM

样式计算的目的是为了计算出DOM节点中每个元素的具体样式,这个阶段大体可分为三步:

  1. 把CSS转换为浏览器能够理解的结构styleSheets
  2. 转换样式表中的属性值,使其标准化
    image
  3. 计算出DOM树中每个节点的具体样式
    image

布局树LayoutTree

  • 创建布局树
  1. 遍历DOM树中的所有可见节点,并把这些节点加到布局中;
  2. 而不可见的节点会被布局树忽略掉,如head标签下面的全部内容,再比如属性包含 dispaly:none的元素,所以这个元素也没有被包进布局树
  • 布局计算
    计算布局树节点的坐标位置

分层树LayerTree

满足以下任意条件,渲染引擎就会为特定的节点创建新的层。

  1. 拥有层叠上下文属性的元素会被提升为单独的一层
    如:明确定位属性的元素、定义透明属性的元素、使用CSS滤镜的元素
    image
  2. 需要剪裁(clip)的地方也会被创建为图层
    如:
    1
    2
    3
    4
    5
    6
    7
    8
    <style>
    div {
    width: 200;
    height: 200;
    overflow:auto;
    background: gray;
    }
    </style>

出现这种裁剪情况的时候,渲染引擎会为文字部分单独创建一个层,如果出现滚动条,滚动条也会被提升为单独的层。
image

栅格化

在有些情况下,有的图层可以很大,比如有的页面你使用滚动条要滚动好久才能滚动到底部,但是通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。

基于这个原因,合成线程会将图层划分为图块(tile),这些图块的大小通常是256x256或者512x512,如下图所示:
image

所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的。

通常,栅格化过程都会使用GPU来加速生成,使用GPU生成位图的过程叫快速栅格化/GPU栅格化,生成的位图被保存在GPU内存中
image

合成与显示

一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。

浏览器进程里面有一个叫viz的组件,用来接收合成线程发过来的DrawQuad命令,然后根据DrawQuad命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。