浏览器渲染流程

浏览器渲染流程

  1. 构建DOM树(Parse HTML):浏览器无法直接理解和使用HTML,需要通过html解析器,处理HTML标记并构造DOM树,可以通过document节点查看
  1. 构建CSSOM树(Parse Stylesheet):和HTML一样,浏览器需要将CSS转换成可以使用的CSSOM,CSS对象模型是一组允许使用JavaScript操作CSS的API,通过CSSOM实现动态读取和修改CSS样式,可以通过document.stylesheets查看

  2. 构建渲染树(Recalculate Style):结合DOM树和CSSOM树,计算 DOM 树中每个节点的样式,涉及css继承规则和层叠规则,这个阶段最终输出的内容是包含样式的DOM树,每个 DOM 节点的样式,可以在devtools的 ComputedStyle 内查看

  3. 构建布局树(Layout):根据渲染树生成布局树,布局树会计算每个DOM元素的大小和几何位置信息,且布局树中只包含可见节点,如display:none的DOM节点不包含在布局树中。第一次确认元素的大小和位置叫布局,之后对节点大小或位置的重新计算叫做回流

  4. 生成渲染层树(Layout): 构建布局树的同时会生成渲染层树,当DOM的LayoutObject在相同的坐标空间时(可以理解为Z轴),将形成渲染层,渲染层树可以理解为层叠上下文,确保页面元素以正确的顺序显示

  5. 生成合成层树(Update Layer Tree):为一些特殊的渲染层生成合成层,不是合成层的其他渲染层与拥有合成层的第一个父级共享合成层,每个合成层对应一个图层(GraphicsLayer),可以在Devtools-Layers面板查看当前的所有图层

    2

  6. 绘制(Paint):为每个图层(GraphicsLayer)生成绘制列表,用来记录绘制指令和绘制顺序,这些指令在分块光栅化的时候才会被执行

  7. 合成层(Composite Layers): 并不是在主线程中合成层,而是把合成层信息提交给合成线程,相当于在合成线程中备份一个图层树,这个操作是同步的,会阻塞主线程。这样做的好处是,有了这份拷贝,合成线程可以不必与主线程交互来进行合成操作。因此,当主线程在进行 Javascript 计算等操作时,合成线程仍然可以正常工作而不被打断。

  8. 生成位图:合成线程接手后,将图层分块,然后光栅化图块(将图块转换为位图),光栅化的方式有两种:

    • 第一种是CPU光栅化:通过光栅化线程池将图块绘制进位图,再将位图作为纹理上传到GPU
    • 第二种是GPU光栅化:浏览器开启了GPU硬件加速,光栅化线程池会将光栅化指令提交给GPU进程,在GPU进程中进行快速光栅化,并保存在GPU内存
  9. 合成:光栅化完成后,所有的纹理都在GPU内存中,GPU会把所有纹理绘制到最终的一个位图里,从而完成纹理的合并

  10. 显示:纹理合并完成后,合成线程会通知浏览器进程,浏览器进程将页面绘制到内存中,最终显示在屏幕上

总结一下,浏览器的渲染机制可以简单理解为“纵向分层,横向分块”。

什么是合成层

WebKit将网页一帧的渲染分为绘制和合成两个步骤。绘制是将网页分层绘制为图像的过程,合成是将所有图像混合在一起后显示在屏幕上的过程。

对于显示器来说,无论当前页面有没有发生变化,它在下一帧总是要刷新的,意味着系统需要提供一个完整的屏幕内容供显示器显示,在这样的场景下,把网页分层,当只有其中一层的内容发生变化时,只用对变化的层执行绘制操作,然后与上一帧中没有变化的图层进行合成,就可以得到最终整个屏幕的内容,避免了绘制操作,与绘制相比,合成是很轻量级的操作,尤其是对于GPU来说。

图层基本都由浏览器去管理,开发者通常不会去干涉,但浏览器回流和重绘的代价很大,在一些情况下合成层能大大提高绘制效率:

  1. 合成层可以减少绘制区域,当需要重绘时,只需要合成层重绘,其他层不受影响

  2. 利用GPU高效实现滚动、3D变换等动画效果,这些动画的执行过程不需要主线程的参与,在纹理合成前,使用 3D API 对合成层进行变形

在Chrome DevTools查看合成层

layer面板
截屏2022-03-01 上午8.24.43

截屏2022-02-26 下午4.04.20

show layer border

截屏2022-02-26 下午4.05.58

Performance

截屏2022-02-26 下午4.01.56

生成合成层的原因(Compositing Reason)

生成合成层的原因有很多,如果想知道全部情况还是需要参考源码,或者参考这边文章Chromium网页Graphics Layer Tree创建过程分析,下面列出一些常见的从渲染层提升为合成层的原因:

直接提升
  1. 硬件加速的iframe元素,video元素,3D canvas元素,2D加速的canvas元素,硬件加速插件(如flash)
  2. 3D变换 transformtranslateZ(0)translate3d(0,0,0)rotateX(0deg)rotateY(0deg)rotateZ(0deg)rotate3d(1,1,145deg)scaleZ(1)scale3d(1,1,1);
  3. backface-visibility`:`hidden;
  4. animation:opacitytransform, filterbackdrop-filter`;
  5. transition设置opacitytransformfilterbackdrop-filter属性的变换,并且被激活状态`;

  6. will-change设置为opacity,transform

  7. will-change设置为lefttoprightbottom,并且设置position定位属性为relativeabsolutefixedsticky的元素
  8. 在高dpi的显示器上,元素的widthheight已知,并且设置position:fixed属性的情况下,元素会自动升为合成层
子元素影响

受子元素影响,父元素可能会被提升为合成层,常见的有以下几种情况:

  1. 子元素包含合成层,并且自身设置了opacity < 1,transform,mask,filter
  2. 子元素宽高超过父元素,父元素的overflow属性为autoscroll,父元素会被提升为合成层,滚动条也被提升为单独的合成层
  3. 子元素是3d变换属性生成的合成层,并且自身具有perspective属性
隐式合成
  1. 层叠:从堆叠顺序上来看,为保持正确的渲染顺序,在合成层上一层的元素会被隐式提升为合成层。

    如下图所示,第一张图是正确的渲染结果,但如果蓝色块被提升为合成层,绿色不提升的话,绿色块将和它的父元素白色块渲染到图一图层上,导致渲染结果出错,这种情况下只能将绿色也提升合成层。

    overlay

  1. 滚动重叠:浏览器判定该元素在元素滚动时可能会和合成层重叠,就将该元素提升为合成层,示例见层爆炸

  2. 假设重叠:元素具有css动画效果,在动画运行期间,动画元素可以重叠其他元素,哪怕目前哪怕视觉上没有重叠,浏览器也会判定假设已经重叠,会将在动画元素之上的元素升级为合成层。

    <style>
      @-webkit-keyframes slide {
        from {
          transform: none;
        }
        to {
          transform: translateX(100px);
        }
      }
      .animating {
        position: fixed;
        width: 300px;
        height: 30px;
        background-color: green;
        animation: slide 5s alternate linear infinite;
      }
      .box {
        width: 600px;
        height: 30px;
        margin-bottom: 5px;
        position: relative;
      }
    </style>
    
    <div class="animating"></div>
    <ul>
      <li class="box">111111111111</li>
      <li class="box">111111111111</li>
      <li class="box">111111111111</li>
      <li class="box">111111111111</li>
      <li class="box">111111111111</li>
      <li class="box">111111111111</li>
      <li class="box">111111111111</li>
      <li class="box">111111111111</li>
    </ul>
    

    截屏2022-02-27 下午6.20.47

层压缩和层爆炸

层压缩

前面介绍的隐式合成,都很有可能在不知不觉的情况下形成大量的合成层,消耗CPU和内存资源,影响页面性能,浏览器也考虑到了这个问题,会对一些非直接原因创建的合成层进行压缩,避免层爆炸。

下面的例子中,红色块div1通过transform: translateZ(0);将自身提升为合成层,绿色块布局在它上面,为了保持正确的渲染顺序,绿色块div2也被提升为合成层,依次类推,后面的蓝色和紫色div也应该被提升,但是浏览器对这种非直接因素的提升做了优化,将绿蓝紫都放在同一个合成层

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    div {
      width: 200px;
      height: 200px;
    }
    .box1 {
      background-color: red;
      transform: translateZ(0);
    }
    .box2 {
      background-color: green;
      position:absolute;
      left: 100px;
      top: 100px;
    }
    .box3 {
      background-color: blue;
      position:absolute;
      left: 150px;
      top: 150px;
    }
    .box4 {
      background-color: purple;
      position:absolute;
      left: 200px;
      top: 200px;
    }
  </style>
</head>
<body>
  <div class="box1">1</div>
  <div class="box2">2</div>
  <div class="box3">3</div>
  <div class="box4">4</div>
</body>
</html>

截屏2022-02-27 下午6.28.49

层爆炸

但还有一些情况,浏览器也无法自动处理压缩,为页面性能埋下隐患,看下面例子,由于滚动元素和合成层重叠,浏览器判定该元素在元素滚动时内部的子元素会和合成层重叠,会将子元素提升为合成层,造成的层爆炸的现象:

<style>
  .animating {
    position: fixed;
    width: 300px;
    height: 30px;
    background-color: green;
  }
  .box {
    width: 600px;
    height: 30px;
    margin-bottom: 5px;
    position: relative;
    /* overflow: hidden; */
  }
</style>
<div class="animating"></div>
<ul>
   <li class="box">111111111111</li>
</ul>

截屏2022-02-27 下午5.29.25

使用建议

应用场景
  1. 合成层的位图会交由 GPU 合成,相比 CPU 处理要快
  2. 将动画效果提升到合成层
  3. 升级为合成层后,执行transformopacity动画不会触发回流和重绘,只会触发合成
谨慎使用
  1. 每个合成层都会占用内存资源,过度使用合成层会带来反向效果
  2. 隐式合成容易在意料之外产生大量合成层,需要特别注意

参考

  1. Chromium网页Graphics Layer Tree创建过程分析
  2. 渲染流程(下):HTML、CSS和JavaScript,是如何变成页面的
  3. How_browsers_work
  4. GPU Accelerated Compositing in Chrome
  5. Front-End Performance Optimization with Accelerated Compositing Part 1
  6. Compositing Layers
  7. stick to compositor only properties and manage layer count
  8. Browser Rendering Optimization