浏览器渲染原理

100 阅读6分钟

我们前端搬砖工天天和浏览器打交道,我们写了布局写了样式,浏览器就哗啦哗啦给我们显示出来,可是显示的背后究竟做了些什么事情呢?我们这里只谈拿到资源之后的事情,首先当浏览器的网络线程接收到html文档后,会产生一个渲染任务,并把渲染任务传递给渲染主线程,在事件循环机制的作用下,渲染主线程开始执行渲染任务,开始渲染流程:

  1. 解析html:生成dom树,生成cssom树
  2. dom树和cssom结合计算
  3. 生成layout
  4. 分层
  5. 绘制
  6. 分块
  7. 光栅化
  8. 完成显示

我们接下来分别解析一下每个步骤都做了些什么事情

解析html(parse Html)

我们拿到了资源,解析html生成dom树、cssom树,浏览器就会生成对应的树形结构,如图

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
      div {
        width: 100px;
        height: 100px;
      }
      div > span {
        font-size: 20px;
        color: white;
      }
    </style>
    <style>
      div {
        background: purple
      }
    </style>
</head>
<body>
  <div>
    <span>111</span>
    <span>222/span>
  </div>
  <p>this is</p>
  <script></script>
</body>
</html>

解析成树

  • dom树:根据页面的标签结构,依次解析成一个树形结构,这个还是比较好理解的,我们平时写的div各种都是相互嵌套,然后就形成了一棵dom树
  • cssom树:
    • css style sheet list:css的样式表,其实有几个种类:
      • 内连样式表:
      • 外部的样式表:
      • 内部样式表:div: { color: red }
      • 浏览器默认样式表:
    • css style rule:这个下面就是各个选择器,选择器下面就是具体的样式设置了

css样式表小技巧

小技巧:除了浏览器的默认样式表,其他的样式表我们都能操作,平时我们可能只是操作内连的样式表,其实其他的两个样式表也可以操作通过

  • document.styleSheets获取的所有样式表
  • document.styleSheets[1].addRule('div', 'background: pink') 为所有的div增加规则,一些ui库应该会用到这些东西

解析遇到js

解析遇到js是会阻塞html的解析的,因为js是有可能会导致html的变动,所以需要先加载执行完js再继续执行html的解析

解析遇到css

解析遇到css是不会阻塞暂停的,渲染主线程会开辟一个预解析器,碰到css如果没有没有下载的(link外部的css),主线程会继续往前执行别的,css的预解析器会去和其他线程配合下载css,完成了下载预解析就会把css解析任务送回渲染主线程,主线程执行到css解析任务就正常解析,如图

计算样式(Computed style)

只是生成了两棵树还不够,需要结合dom树和cssom树计算出样式,例如div这个的宽高背景是什么,要两棵树结合生成有样式的dom树,所以渲染主线程会遍历dom树,依次为每个dom节点计算出出compute style浏览器里面可以看到:

  • 会把颜色转成rgb,会把你的rem转成px等等

生成布局树(layout tree)

根据元素几何信息增减

元素的数值都计算好了,但是还不够就是layout tree和计算样式合成的dom树不是一一对应的,例如:

  1. dom树有个元素样式是diaply: none,那么layout tree就不会有这个元素
  2. css设置了伪类元素::before,::after这些,那么layout tree会加上这些元素

layout tree是要生成每个节点的几何信息,上面两个点,一个是没有几何信息所以去掉,伪类有几何信息所以要加上

自动生成匿名盒子

  1. 会给你生成一些匿名行盒和匿名块盒,这点怎么理解呢,因为你的html和css的语义不完全匹配,浏览器为了符合css规范,就会自动插入一些匿名盒子,规则如下
    • 内联元素(inline)或文本直接出现在一个**块级盒子(block box)**中,而这些内联内容并没有被内联容器包裹时,浏览器会自动生成一个匿名的块盒来容纳这些内容。(CSS 的规则要求,块盒只能包含块级子项或一个匿名块盒,不能直接混合内联内容,因此会自动生成匿名块盒。)
<div>
  Hello <span>world</span>
</div>

<div>
  <匿名块盒>
    Hello
    <span>world</span>
  </匿名块盒>
</div>
    • 当块盒中包含文本节点或内联内容,但这些内容没有包裹在显式的内联盒中时,会生成匿名行盒(为了正确计算 line box(行框)位置、对齐、换行等问题,浏览器必须将文本包装成 inline box。)
<p>This is some text.</p>

<p>
  <匿名行盒>
    This is some text.
  </匿名行盒>
</p>
    • 当一个容器中同时包含块级元素和内联元素时,浏览器会自动将内联部分包在匿名块盒中以分隔它们。
<div>
  Some text
  <div>A block</div>
  More text
</div>

<div>
  <匿名块盒>Some text</匿名块盒>
  <div><匿名行盒>A block<匿名行盒></div>
  <匿名块盒>More text</匿名块盒>
</div>

分层(Layer)

接着主线程会有个复杂的策略对整个layout tree进行分层,分层的好处在于以后发生改变可以只改变当前的层级,提高效率。滚动条、transform、opacity等样式都会影响分层的结果,也可以使用will-change的css属性去影响分层结果,但是不一定你用了就会分层

控制台工具layers里面可以看到分层:

绘制(paint)

为每一层的分层生成如何绘制的指令,然后渲染主线程的工作就到此为止了,剩余的工作就交给其他线程了

分块(Tile)、光栅化(Raster)、画(draw)

  • 由合成线程把每一层分成多个小块,分块完成后会产生多个tile(瓦片),然后会把title下发给光栅线程池(Raster Worker Pool)由多个线程执行,这里实际就是调用gpu的多个线程完成光栅化,就是把每个tile变成位图,是有像素信息的
  • 看到下图,page里面有个view就是我们的视窗,我们会优先去进行靠近视窗块的光栅化

  • 光栅化得到位图之后,合成线程拿到位图就会生成一个个的指引(quad)然后交给gpu,gpu就会调用硬件去画出显示在屏幕的东西