浏览器渲染原理

发布于:2024-12-06 ⋅ 阅读:(29) ⋅ 点赞:(0)

当浏览器的网络线程收到HTML文档之后,会产生一个渲染任务并且会将其传递给渲染主线程的消息队列,在事件循环机制的作用下,渲染的主线程会将消息队列中的渲染任务取出来进行渲染流程。其中前五步骤为渲染的主线程。
如果想更了解浏览器的事件循环机制,请转接这篇文章

第一步解析Html

浏览器为了提高解析效率,会在开始解析之前进行一个预解析的线程,该线程主要任务就是预解析下载外部的CSS样式和外部JS样式,在解析完成后,会生成DOM树和CSSOM树,CSSOM树包括行内样式,默认样式,内部样式,外部样式。

为什么CSS不会阻塞HTML解析,而JS会阻塞HTML解析进程???

如果主线程解析到link的位置,而此时外部的CSS样式并没有解析下载完成,主线程不会等待会直接解析后面的HTML,这是因为CSS外部样式的解析下载是在预解析线程中进行的;
在这里插入图片描述

而如果解析进行到script的位置,此时主线程会停下等待JS文件解析下载好,之后再进行后续的HTML解析,这是因为JS的代码执行过程中可能会更改DOM树的结构,所以DOM树的生成必须暂停;

在这里插入图片描述
如果遇到的script标签带有deferasync属性;

  • defer 下载不阻塞页面渲染,等到HTML文档解析完成之后,DOMContentLoaded事件的触发前, 才执行该脚本
  • async 下载不阻塞页面渲染,但是会停止HTML文档的解析,先执行脚本之后再进行HTML解析

第二步样式计算

主线程会遍历得到的DOM树,依次为树中的每个节点计算出他们的最终样式,这个被称为Computed Style,在这个过程中,预设值会变成绝对值,相对单位会变成绝对单位;例如:red会变成rgb(255,0,0)em会变成px,随后就会得到一颗带有样式的DOM树。

第三步布局

布局完成后会得到布局树(layout树)。在布局阶段会依次遍历 DOM 树的每一个节点,计算每个节点的几何信息。

大部分时候,DOM 树和布局树并非一一对应。
DOM树:与 HTML 标签一一对应,包括 head 和隐藏元素
布局树:不包括 head 和隐藏元素,布局树只会展示 DOM 树下的几何信息

比如display:none的节点没有几何信息,因此不会生成到布局树;又比如使用了伪元素选择器,虽然 DOM 树中不存在这些伪元素节点,但它们拥有几何信息,所以会生成到布局树中。

第四步分层

分层的好处在于,将来某一个层改变后,仅会对该层进行后续处理,从而提升效率。

主线程需要遍历布局树来创建一棵层次树(Layer树), 滚动条,堆叠上下文, transformopacity 等样式或多或者的影响分层结果,我们可以通过 will-change 属性更大程度的影响分层结果。如果在页面中我们有一个元素,要经常重排重绘,我们可以通过 will-change: transform;告诉浏览器,将来元素会发生变化,让浏览器自行决策是否进行单独分层。

第五步绘制

主线程会为每个层单独产生绘制指令集,用于描述这一层的内容该如何画出来。

在完成绘制后,主线程的任务宣告结束。,之后主线程会将每个图层的绘制信息交给合成线程来完成后续工作。

什么是合成线程呢?

合成是一种将页面分成若干层,然后分别对它们进行光栅化,最后在一个单独的线程 - 合成线程里面合并成一个页面的技术。当用户滚动页面时,由于页面各个层都已经被光栅化了,浏览器需要做的只是合成一个新的帧来展示滚动后的效果罢了。页面的动画效果实现也是类似,将页面上的层进行移动并构建出一个新的帧即可。

第六步分块

启动合成线程,每个图层进行分块,将其划分为更多的小区域

它会从线程池中拿取多个线程来完成分块工作。

第七步光栅化

一旦Layer 树被创建,渲染顺序被确定,主线程会把这些信息通知给合成器线程,合成器线程开始对层次数的每一层进行光栅化。有的层的可以达到整个页面的大小,所以合成线程需要将它们切分为一块又一块的小图块,之后将这些小图块分别进行发送给一系列光栅线程进行光栅化,结束后光栅线程会将每个图块的光栅结果存在GPU Process的内存中。为了优化显示体验,那些在视口中的或者视口附近的层先被光栅化。

文档结构、元素的样式、元素的几何关系、绘画顺序,这些信息我们都有了,这个时候如果要绘制一个页面,我们需要做的是把这些信息转化为显示器中的像素,这个转化的过程,叫做光栅化。

第八步画

合成线程拿到每个层、每个块的位图后,生成一个个指引信息。指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转、缩放等变形。

变形发生在合成线程,与渲染主线程无关,这就是transform效率高的本质原因。

合成线程会把 指引信息提交给 GPU 进程,由 GPU 进程产生系统调用,提交给 GPU 硬件,完成最终的屏幕成像。

常见面试题

什么是回流reflow?

当DOM变化影响了元素,比如元素的尺寸、布局、显示隐藏等改变了,需要重写构建。每个页面至少需要一次回流,就是在页面第一次加载的时候,这个时候一定会发生回流。为了避免连续的多次操作导致布局树反复计算,浏览器会合并这些操作,当 JS 代码全部完成后再进行统一计算。所以,改动属性造成的reflow 是异步完成的。当 JS 获取布局属性时,就可能造成无法获取到最新的布局信息,所以浏览器会在获取属性时立即 reflow

什么是重绘repaint?

当一个元素的外观发生变化(可见样式更改),但是没有改变布局,重新渲染元素的外观。比如background-colorcolor。由于元素的布局信息也属于可见样式,所以reflow 一定会引起 repaint

如何避免回流重绘:

  • 避免使用table布局
  • 尽可能在DOM树的最末端改变class
  • 不要频繁的操作元素的样式
  • 避免设置多层内联样式
  • 开启GPU加速
  • 使用absolute或者fixed,脱离标准文档流