渲染流程: HTML CSS JS 是如何变成页面的? - 图1

渲染流水线总结:

构建 DOM 树 -> 构建 CSSOM 树(styleSheets) -> 合成布局树(layoutTree) -> 分层 -> 绘制 -> 光栅化(图块转化成位图) -> 合成 -> 显示

  1. 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。

渲染流程: HTML CSS JS 是如何变成页面的? - 图2

  1. 样式计算:

    1. 属性值标准化: 颜色十六进制 / rem em -> px …
    2. 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。

渲染流程: HTML CSS JS 是如何变成页面的? - 图3

  1. 创建布局树,并计算元素的布局信息。

    1. 遍历 DOM 树中的所有可见节点,并把这些节点加到布局树中;
    2. 不可见的节点会被布局树忽略掉, 如: head …

渲染流程: HTML CSS JS 是如何变成页面的? - 图4

  1. 对布局树进行分层,并生成分层树。

渲染流程: HTML CSS JS 是如何变成页面的? - 图5

  1. 因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)
  2. 创建新图层的条件
    1. 第一点,拥有层叠上下文属性的元素会被提升为单独的一层。
      • 文档根元素(html);
        • position 值为 absolute(绝对定位)或 relative(相对定位)且 z-index 值不为 auto 的元素;
        • position 值为 fixed(固定定位)或 sticky(粘滞定位)的元素(沾滞定位适配所有移动设备上的浏览器,但老的桌面浏览器不支持);
        • flex (flexbox) 容器的子元素,且 z-index 值不为 auto;
        • grid (grid) 容器的子元素,且 z-index 值不为 auto;
        • opacity 属性值小于 1 的元素(参见 the specification for opacity);
        • mix-blend-mode 属性值不为 normal 的元素;
        • 以下任意属性值不为 none 的元素:
            • transform - filter - perspective - clip-path - mask / mask-image / mask-border - isolation 属性值为 isolate 的元素;
            • -webkit-overflow-scrolling 属性值为 touch 的元素;
            • will-change 值设定了任一属性而该属性在 non-initial 值时会创建层叠上下文的元素(参考这篇文章);
            • contain 属性值为 layout、paint 或包含它们其中之一的合成值(比如 contain: strict、contain: content)的元素。

渲染流程: HTML CSS JS 是如何变成页面的? - 图6

  1. 2. 第二点,需要剪裁(clip)的地方也会被创建为图层。

<style>
      div {
            width: 200;
            height: 200;
            overflow:auto;
            background: gray;
        } 
</style>
<body>
    <div >
        <p>所以元素有了层叠上下文的属性或者需要被剪裁,那么就会被提升成为单独一层,你可以参看下图:</p>
        <p>从上图我们可以看到,document层上有A和B层,而B层之上又有两个图层。这些图层组织在一起也是一颗树状结构。</p>
        <p>图层树是基于布局树来创建的,为了找出哪些元素需要在哪些层中,渲染引擎会遍历布局树来创建层树(Update LayerTree)。</p> 
    </div>
</body>

在这里我们把 div 的大小限定为 200 200 像素,而 div 里面的文字内容比较多,文字所显示的区域肯定会超出 200 200 的面积,这时候就产生了剪裁,渲染引擎会把裁剪文字内容的一部分用于显示在 div 区域,下图是运行时的执行结果:
渲染流程: HTML CSS JS 是如何变成页面的? - 图7

  1. 为每个图层生成绘制列表,并将其提交到合成线程。
    1. 把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表,如下图所示:

渲染流程: HTML CSS JS 是如何变成页面的? - 图8

  1. 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。

    1. 在有些情况下,有的图层可以很大,比如有的页面你使用滚动条要滚动好久才能滚动到底部,但是通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。合成线程会将图层划分为图块(tile),这些图块的大小通常是 256x256 或者 512x512,如下图所示:

渲染流程: HTML CSS JS 是如何变成页面的? - 图9

通常一个页面可能很大,但是用户只能看到其中的一部分,我们把用户可以看到的这个部分叫做视口(viewport)。

  1. 然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的

渲染流程: HTML CSS JS 是如何变成页面的? - 图10

  1. 栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。

渲染流程: HTML CSS JS 是如何变成页面的? - 图11

  1. 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。

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

  1. 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。

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

image.png

到这里,经过这一系列的阶段,编写好的 HTML、CSS、JavaScript 等文件,经过浏览器就会显示出漂亮的页面了。