Skip to content

渲染进程是浏览器最重要的进程, 本文将是对Inside look at modern web browser (part 3) | Blog | Chrome for Developers的重复。

1. html文件解析(parse HTML)

渲染进程收到.html文件时, 会开始html内容解析为DOM(Document Object Model)。

DOM是浏览器对网页的抽象, 是web程序员通过javascript对网页操作的一套数据结构和api。

1. 1 资源加载

html可能会引用外部的图片、css、js等资源(通过link、img、script等等标签引用), 这些资源需要网络线程去进行网络请求, 在html解析的过程中会找到他们将任务发送到网络线程。

javascript的请求可能会阻塞解析:

浏览器遇到script标签时, 会停止解析, 在 加载 -> 解释 -> 执行 后才会继续。

阻塞解析的原因是, 引用的javascript代码可能会执行document.write等api更改了整个dom结构, 从而更改了document的形状; 从HTML Standard (whatwg.org)中可以看出, js增加了新的html片段后, 需要重新tokenizer

截屏2024-07-08 下午4.18.35

当你的javascript中没有使用document.wire()时, 可以向 <script> 标记添加 asyncdefer 属性或使用es modules。然后,浏览器会异步加载并运行 JavaScript 代码,而不会阻止解析。

async和defer区别?

  • defer告诉浏览器不需要阻塞解析加载运行此脚本, 而是在文档解析完毕, 但在DOMContentLoaded钩子之前执行。
    • 包含 defer 属性的脚本将阻塞 DOMContentLoaded 事件触发,直到脚本完成加载并执行。
    • 包含 defer 属性的脚本会按照它们出现在文档中的顺序执行。
    • esm模块无需使用defer, 它们默认就是defer
  • async也可以解除浏览器对解析的阻塞,async 脚本会被并行请求,并尽快解析和执行。

总之async和defer都可以解除阻塞, 但执行策略和时机不同。

2. 样式计算(Recaculate Style)

计算样式

此过程会使用文档中或引用的所有css, 以及dom结点的默认样式, 计算得到每一个结点的样式表。 即浏览器开发者工具css中computed的内容。

3. 布局(layout)

layout

主线程会遍历 DOM 和计算出的样式,并创建布局树,其中包含 x y 坐标和边界框大小等信息。布局树的结构可能与 DOM 树类似,但它仅包含与页面上可见内容相关的信息。如果应用了 display: none,则该元素不属于布局树(然而,具有 visibility: hidden 的元素在布局树中)。同样,如果应用包含类似 p::before{content:"Hi!"} 的伪元素,它就会包含在布局树中,即使它不在 DOM 中也是如此。

确定网页的布局是一项具有挑战性的任务。即使是最简单的页面布局(例如从上到下的块流),也必须考虑字体大小和换行位置,因为它们会影响段落的大小和形状;而后又会影响下一段落的位置。

CSS 可以使元素悬浮到一侧、遮盖溢出内容以及更改书写方向。可以想象,这个布局阶段有一项繁重的任务。在 Chrome 中,有一整个工程师团队负责开发布局。如果您想详细了解他们的工作,可以观看 BlinkOn Conference 的一些演讲,并且非常有意思。

讲的太好了, 原样搬来

layout的过程计算得到了dom元素被绘制在屏幕上所需的几何信息, 如位置、形状等等。

4. paint

这里的paint并不是绘制, Paint的任务是整理每一层页面的绘制信息,构成绘制列表,这些数据会交给合成线程负责后续绘制操作。

拥有 DOM、样式和布局仍然不足以渲染页面。假设您正尝试复制一幅画。您已经知道元素的大小、形状和位置,但仍需判断它们的绘制顺序。

Z-index 未通过

在paint流程中, 会生成具体的一个绘制流程/列表, 就像先画背景, 然后是文本, 然后是矩形一样。 绘制列表会交给合成线程, 由合成线程进行真正的绘制。

5. draw / 合成(compositing)

现在渲染进程得到了文档结构、每个元素样式、几何图形信息以及绘制顺序, 是时候把它真正的显示在屏幕上了。

众所周知, 浏览器页面靠的不断更新(60/s), draw的过程就是控制帧如何生成。

NOTE

以下结尾照搬

什么是合成

合成是一种技术,可将网页的各个部分分离成图层,分别将它们光栅化,然后在单独的线程(称为“合成器线程”)中合成为网页。如果发生滚动,由于图层已光栅化,因此只需合成新帧即可。同样,可以通过移动层和合成新帧来实现动画效果。

您可以在开发者工具中使用“Layers”面板查看网站是如何划分为多个图层的。

划分为层

为了确定哪些元素需要位于哪些层,主线程会遍历布局树来创建层树(此部分在开发者工具性能面板中称为“Update Layer Tree”)。如果网页中本应属于单独图层的某些部分(例如滑入式侧边菜单)没有出现,您可以使用 CSS 中的 will-change 属性来提示浏览器。

image-20240708202247934图 16:遍历布局树生成层树的主线程

您可能很想为每个元素都添加层,但与每帧将页面的一小部分光栅化相比,跨过多层进行合成可能会导致操作速度缓慢,因此请务必衡量应用的渲染性能。如需详细了解此主题,请参阅坚持仅合成器的属性和管理层计数

主线程外的光栅和合成

创建层树并确定绘制顺序后,主线程会将该信息提交到合成器线程。然后,合成器线程会光栅化每个图层。图层的大小可能相当于页面的全部长度,因此合成器线程会将它们分成多块图块,并将每个图块发送到光栅线程。光栅线程会光栅化每个图块并将它们存储在 GPU 内存中。

image-20240708202238177图 17:创建图块位图并发送到 GPU 的光栅线程

合成器线程可以优先处理不同的光栅线程,以便先对视口内(或附近)的内容进行光栅化。图层还具有多个针对不同分辨率的平铺处理,以处理放大操作等操作。

图块光栅化后,合成器线程会收集图块信息(称为“绘制四边形”)来创建合成器框架

绘制四边形包含功能块在内存中的位置,以及要在页面中的什么位置绘制功能块(考虑页面合成)等信息。
合成器框架表示页面帧的绘制四边形集合。

然后,通过 IPC 将合成器帧提交到浏览器进程。此时,可以从界面线程(针对浏览器界面更改)或针对扩展程序的其他渲染程序进程添加另一个合成器帧。系统会将这些合成器帧发送到 GPU,以便在屏幕上显示。如果发生滚动事件,合成器线程会再创建一个合成器帧以发送到 GPU。

创建合成帧的合成器线程。先将帧发送到浏览器进程,然后再发送到 GPU

合成的好处是,

在不涉及主线程的情况下完成。合成器线程不需要等待样式计算或 JavaScript 执行。因此,仅合成动画被认为是实现流畅性能的最佳选择。如果需要再次计算布局或绘制,则必须涉及主线程。

eg. Reflow(重排)和Repaint(重绘)

当使用javascript进行dom操作时, 一些变化可能使得浏览器需要重新进行布局或者重新绘制。

重排发生在浏览器需要重新计算网页的某些部分的位置和几何形状时, 对应着layout过程。

重绘是浏览器重新绘制网页, 以显示由 UI 更改引起的视觉更新, 对应着paint和compositing的过程。

重排发生在重绘之前, 因此重排的成本包括了重新布局和重绘, 。

何时发生重排 (Reflow)?

重排是影响浏览器性能很重要的关键因素,因为它可能导致整个或部分页面的布局更新; 可能因为一个节点大小的改变,就会触发整的页面的回流。例如:改变 widthheightfont-size 等。

何时发生重绘 (Repaint)?

当页面上的某个元素需要改变颜色或其他不影响布局的属性时,浏览器会对其进行重绘 (repaint)。与回流不同,重绘不会影响页面布局,但是也会影响页面的性能。例如:改变 outlinevisibilitycolorbackground-color等。

尽可能减少浏览器重排

  1. 减少不必要的 DOM 深度。在 DOM 树中的一个级别进行更改可能会致使该树的所有级别(上至根节点,下至所修改节点的子级)都随之变化。这会导致花费更多的时间来执行重排。
  2. 尽可能减少 CSS 规则的数量,并移除未使用的 CSS 规则。
  3. 如果您想进行复杂的渲染更改(例如动画),请在流程外执行此操作。您可以使用 position-absolute 或 position-fixed 来实现此目的。
  4. 避免使用不必要且复杂的 CSS 选择器(尤其是后代选择器),因为此类选择器需要耗用更多的 CPU 处理能力来执行选择器匹配。