浏览器:内核组成部分和简单渲染原理

发布于 2024-04-21  1154 次阅读


内核

简单来说浏览器内核是通过取得页面内容、整理信息(应用CSS)、计算和组合最终输出可视化的图像结果,通常也被称为渲染引擎。

浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:

  • GUI 渲染线程
  • JavaScript引擎线程
  • 定时触发器线程
  • 事件触发线程
  • 异步http请求线程

1.GUI渲染线程

  • 主要负责页面的渲染,解析HTML、CSS,构建DOM树,布局和绘制等。
  • 当界面需要重绘或者由于某种操作引发回流时,将执行该线程。
  • 该线程与JS引擎线程互斥,当执行JS引擎线程时,GUI渲染会被挂起,当任务队列空闲时,主线程才会去执行GUI渲染。

2.JS引擎线程

  • 该线程当然是主要负责处理 JavaScript脚本,执行代码。
  • 也是主要负责执行准备好待执行的事件,即定时器计数结束,或者异步请求成功并正确返回时,将依次进入任务队列,等待 JS引擎线程的执行。
  • 当然,该线程与 GUI渲染线程互斥,当 JS引擎线程执行 JavaScript脚本时间过长,将导致页面渲染的阻塞。

3.定时器触发线程

  • 负责执行异步定时器一类的函数的线程,如: setTimeout,setInterval。
  • 主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待JS引擎线程执行。

4.事件触发线程

  • 主要负责将准备好的事件交给 JS引擎线程执行。

比如 setTimeout定时器计数结束, ajax等异步请求成功并触发回调函数,或者用户触发点击事件时,该线程会将整装待发的事件依次加入到任务队列的队尾,等待 JS引擎线程的执行。

5.异步http请求线程

  • 负责执行异步请求一类的函数的线程,如: Promise,axios,ajax等。
  • 主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待JS引擎线程执行。

渲染

在解析阶段构建的树(DOM、CSSOM)被组合成一种叫做渲染树的东西。 这用于计算最终将绘制到屏幕上的所有可见元素的布局。 渲染树的目的是确保页面内容以正确的顺序绘制元素。 它将作为在屏幕上显示像素的绘画过程的输入。

DOM 和 CSSOM 是使用 HTML 和 CSS 文件创建的。 这两个文件包含不同类型的信息,树的结构也不同,那么渲染树是如何创建的呢?

结合 DOM 和 CSSOM

  • 浏览器将开始在 DOM 树的根部施展魔法并遍历每个可见节点。 一些节点,如脚本或元标记是不可见的,因此它们被忽略。 还有一些节点会被 CSS 隐藏(例如 display: "none" 属性),它们也会被忽略。 我们只对可见节点感兴趣,因为只有它们对屏幕上的输入有影响。
  • 对于在 DOM 中找到的每个可见节点,将在 CSSOM 中找到相应的规则并应用它们。

以上步骤的结果将是一个包含所有可见节点、内容和样式的渲染树

布局(回流)阶段

渲染树包含有关显示哪些节点及其计算样式的信息,但不包含每个节点的尺寸或位置。

接下来需要做的是计算这些节点在设备视口(浏览器窗口内)内的确切位置及其大小。 这个阶段称为布局(在 Chrome、Opera、Safari 和 Internet Explorer 中)或重排(在 Firefox 中),但它们的意思相同。 浏览器在渲染树的根部开始这个过程并遍历它。

回流步骤不会只发生一次,而是每次我们更改 DOM 中影响页面布局的某些内容时,即使是部分更改,都会触发回流。 重新计算元素位置的情况示例如下:

  • 在 DOM 中添加或删除元素
  • 调整浏览器窗口大小
  • 更改元素的宽度、位置或使其浮动

让我们来看一个非常基本的 HTML 示例,其中内嵌了一些 CSS:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Reflow</title>
  </head>
  <body>
    <div style="width: 100%; height: 50%">
      <div style="width: 50%; height: 50%">This is the reflow stage!</div>
    </div>
  </body>
</html>

上面的代码只是说在视口内我们应该有两个 div,其中第二个嵌套在第一个里面。 父 div 占据视口宽度的 100%和高度的 50%。第二个 div 占据父 div 的 50% 这看起来像这样:

这个过程的输出是一个类似盒子的模型,它准确地捕获了每个元素需要在屏幕上的位置及其大小。 完成此步骤后,输出就可以传递到下一步,称为绘画阶段

绘画(重绘)阶段

在浏览器决定哪些节点需要可见并计算出它们在视口中的位置后,就可以在屏幕上绘制它们(渲染像素)了。 这个阶段也被称为光栅化阶段,浏览器将在布局阶段计算的每个盒子转换为屏幕上的实际像素。

就像布局阶段一样,绘画阶段不会只发生一次,而是每次我们改变屏幕上元素的外观时。 这些情况的例子是:

  • 改变元素的轮廓
  • 改变背景颜色
  • 改变不透明度或可见性

绘画意味着浏览器需要将元素的每个视觉部分绘制到屏幕上,包括文本、颜色、边框、阴影和替换元素(如按钮和图像),并且需要超快地完成。 为了确保重绘可以比初始绘制更快地完成,屏幕上的绘图通常被分解成几层。 如果发生这种情况,则需要进行合成。

分层和合成

传统意义上,网络浏览器完全依赖 CPU 来呈现网页内容。 但现在即使是最小的设备也有高性能的 GPU,所有大部分实现方案都围绕着 GPU 来寻求更好的体验。

合成是一种将页面的各个部分分成层的技术,分别绘制它们并在称为合成器线程的单独线程中合成为页面。 当文档的各个部分绘制在不同的层中并相互重叠时,合成是必要的,以确保它们以正确的顺序绘制到屏幕上并且内容被正确呈现。

通常,只有特定的任务会被重定向到 GPU,而这些任务可以由合成器线程单独处理。

为了找出哪些元素需要在哪一层,主线程遍历布局树并创建层树。 默认情况下,只有一层(这些层的实现方式因浏览器而异),但我们可以找到会触发重绘的元素,并为每个元素创建一个单独的层。 这样,重绘不应应用于整个页面,而且此过程将可以使用到 GPU

如果我们想向浏览器提示某些元素应该在一个单独的层上,我们可以使用 will-change CSS 属性。 实际上有一些特定的属性和元素表示新层的创建。 其中一些是 <video><canvas> 和任何具有 CSS opacity 属性、3D transformwill-change 和其他一些属性的元素。 这些节点连同它们的后代将被绘制到它们自己的图层上。


欢迎欢迎~热烈欢迎~