关键渲染路径

690 阅读11分钟

关键渲染路径(Critical Rendering Path)。先读MDN文档。

渲染步骤如下:

crp.png

  1. Network获取资源:浏览器首先会根据用户输入的 URL 发起网络请求,获取 HTML、CSS、JavaScript 等资源;
  2. HTML ⇒ DOM解析 HTML:浏览器通过解析 HTML 构建 DOM 树(文档对象模型,Document Object Model),DOM 树是由 HTML 元素及其属性组成的一个树形结构,表示了文档的内容和结构;
  3. CSS ⇒ CSSOM解析 CSS:浏览器通过解析 CSS 构建 CSSOM 树(CSS 对象模型,CSS Object Model),CSSOM 树是由 CSS 规则及其样式属性组成的一棵树,表示了文档的样式和布局;
  4. Render Tree构建渲染树:浏览器将 DOM 树和 CSSOM 树合并成渲染树,渲染树只包括需要显示在屏幕上的元素,也就是可见的元素;
  5. Layout布局:浏览器根据渲染树计算每个元素的位置和大小,布局也称为回流(reflow);
  6. Paint绘制:浏览器根据渲染树和布局信息将页面渲染到屏幕上,也称为重绘(repaint)。

第一步Network的过程就是从输入URL到页面展示这中间发生了什么

接下来从第二步开始从概念和可优化方案讲讲每一步的过程

DOM的生成

生成 DOM 的过程可以分为两个主要的步骤:解析 HTML 和构建 DOM 树。

解析 HTML:浏览器会将从服务器获取到的 HTML 文件解析成一系列的标记(tokens)。

解析过程包括以下几个步骤:

  1. 标记化(Tokenization):将 HTML 字符串按照标签、属性、文本等标记进行切割,生成一系列的标记;
  2. 构建节点(Building Nodes):将标记转化为节点,包括元素节点、文本节点和注释节点;
  3. 建立节点之间的关系(Attachment):将节点按照其在 HTML 中的嵌套关系建立起来,形成一棵 DOM 树。

构建 DOM 树:DOM 树是由 HTML 元素及其属性组成的一个树形结构,表示了文档的内容和结构。

在构建 DOM 树的过程中,浏览器会执行以下步骤:

  1. 创建根节点:DOM 树的根节点是文档节点(Document),表示整个文档;
  2. 创建元素节点:解析 HTML 标记时遇到个元素标签,就会创建一个相应的元素节点,并将其添加到 DOM 树中;
  3. 创建属性节点:解析 HTML 标记时遇到一个属性,就会创建一个相应的属性节点,并将其添加到相应的元素节点中;
  4. 创建文本节点:解析 HTML 标记时遇到一段文本,就会创建一个相应的文本节点,并将其添加到相应的元素节点中;
  5. 创建注释节点:解析 HTML 标记时遇到一段注释,就会创建一个相应的注释节点,并将其添加到相应的元素节点中。

在此阶段可能遇到的问题(JavaScript)

在解析 HTML 文件时,如果遇到 JavaScript 文件,浏览器会异步下载该文件,但不会阻塞 HTML 的解析和 DOM 树的构建。当 JavaScript 文件下载完成后,浏览器会执行该文件中的代码,可能会修改 DOM 树的结构或样式,从而导致重排和重绘。为了避免这种情况,通常将 JavaScript 文件放在 HTML 文件的底部,也就是在 标签之前,以确保 JavaScript 在 DOM 树构建完成后再执行。

优化

在该阶段可做最大的优化就是减少回流(reflow)重绘(repaint)

  1. 将多次 DOM 操作合并成一次操作,避免重复触发回流(reflow)重绘(repaint)
  2. 避免使用样式属性(例如 width、height、left、top 等)对元素进行多次修改,可以使用 CSS 类名来控制元素的样式,避免重复计算元素的样式;
  3. 使用绝对定位、固定定位等方式,将节点脱离文档流,从而减少触发重排和重绘;
  4. JavaScript 文件放在 HTML 文件的底部最后加载。

(可忽略)在构建 DOM 树的过程中,浏览器也会自己执行许多优化措施无法手动控制,例如:

  1. 采用流式解析,一边解析一边生成节点,避免一次性解析整个文档;
  2. 采用异步解析,将文档的解析和 DOM 树的构建分为两个步骤,避免长时间阻塞渲染线程;
  3. 遇到错误标记时,采用容错机制,尽可能地解析出合法的 HTML。

CSSOM的生成

💡 CSSOM 跟 DOM 很像,但是不同。DOM 构造是增量的,CSSOM 却不是。

CSS 是渲染阻塞的:浏览器会阻塞页面渲染直到它接收和执行了所有的 CSS。

CSS 为什么是渲染阻塞:是因为规则可以被覆盖,所以内容不能被渲染直到 CSSOM 的完成。

CSSOM 的生成步骤如下:

  1. 解析 CSS 文件和 style 标记中的样式信息,并将其转化为浏览器可以理解的格式,例如:样式规则(rule)、选择器(selector)、属性(property)和值(value)等。
  2. 根据选择器匹配元素,生成匹配规则(matched rule)。
  3. 根据匹配规则计算出最终的样式信息,并生成对应的 CSSOM 树。

在此阶段可能遇到的问题

JavaScript 可以对样式信息进行读取、修改和删除等操作,从而实现对页面样式的动态修改和更新。常见的操作包括:

  1. 读取元素的样式信息:通过访问元素的 style 属性或使用 getComputedStyle() 方法来读取元素的计算样式信息。
  2. 修改元素的样式信息:通过修改元素的 style 属性或使用 JavaScript 代码动态生成 CSS 规则来修改元素的样式信息。
  3. 删除元素的样式信息:通过将元素的 style 属性设置为空或删除 CSS 规则来删除元素的样式信息。

CSS 渲染阻塞造成的后果:

  1. 白屏时间过长:由于浏览器需要等待 CSS 文件下载和解析完成后才能开始页面渲染,因此会导致页面的白屏时间过长,用户体验较差。
  2. 页面加载时间延长:由于 CSS 文件的下载和解析需要一定的时间,因此会导致页面的加载时间延长,影响用户的使用体验。
  3. 页面渲染速度变慢:由于浏览器需要等待 CSS 文件下载和解析完成后才能开始页面渲染,因此会导致页面的渲染速度变慢,影响用户的使用体验。

优化:(css方面的优化提升很小,不是主要发力点)

  1. 将样式表放到文档的头部:将样式表放到文档的头部,可以使得浏览器尽早的开始解析和生成 CSSOM,从而提高页面的渲染速度。
  2. 避免使用 @import:@import 指令会阻止页面的并行下载,从而降低页面的加载速度,应该尽量避免使用。
  3. 避免使用不必要的选择器:过于复杂或嵌套的选择器会增加浏览器的匹配和计算时间,从而降低页面的性能。
  4. 避免使用复杂的 CSS 选择器:因为复杂的选择器需要更多的计算才能匹配到对应的元素,从而增加了渲染树的构建时间。
  5. 避免使用样式表中的表达式:因为表达式需要在每次页面重新渲染时都重新计算一次,从而降低了页面的渲染性能。
  6. 使用媒体查询和响应式设计:使用媒体查询和响应式设计可以根据不同设备和屏幕大小来为元素应用不同的样式,从而提高页面的可用性和用户体验。
  7. 避免过度的样式操作:过于频繁和复杂的样式操作可能会触发页面的重绘和重排,从而影响页面的性能,应该尽量避免。可以考虑使用一些技术手段,如:使用 CSS3 动画代替 JavaScript 动画、使用 transform 和 opacity 属性来进行动画效果等。

Render Tree

渲染树包括了内容和样式:DOM 和 CSSOM 树结合为渲染树。步骤如下:

  1. 将 DOM 树和 CSSOM 树合并为 Render Tree。
  2. 在合并的过程中,浏览器会根据 DOM 树上的节点和 CSSOM 树上的规则,检查每个节点,从 DOM 树的根节点开始,并且决定哪些 CSS 规则被添加,计算每个节点在 Render Tree 中的位置和样式信息。
  3. Render Tree 中包含了需要在屏幕上显示的节点,包括可见元素和隐藏元素。而不包括不需要显示的节点,比如 <head><meta><script> 等元素,如果有元素上有 display: none;,它本身和其后代都不会出现在渲染树中
  4. Render Tree 中,每个节点都是一个带有样式信息的盒子,包括盒子的位置、大小、背景色、字体大小、边框等信息。
  5. Render Tree 中的每个节点都对应着屏幕上的一个矩形区域,也就是布局过程中的盒模型

优化:

在该阶段的优化取决于生成DOM和CSSOM阶段,减少DOM、CSSOM的操作即可。

Layout

一旦渲染树被构建,布局变成了可能。布局取决于屏幕的尺寸。布局这个步骤决定了在哪里和如何在页面上放置元素,决定了每个元素的宽和高,以及他们之间的相关性。步骤如下:步骤如下:

  1. 计算文档的布局根节点(Layout Root),通常是 <html> 元素。
  2. 遍历 Render Tree,对于每个节点,根据它的样式信息和布局信息,计算出它的位置和大小等属性,并创建布局盒子模型(Layout Box)。
  3. 对于每个节点的子节点,重复步骤2。
  4. 计算出每个节点在 Render Tree 中的位置,包括它们在屏幕上的坐标和大小。
  5. 将每个节点的布局信息存储在布局盒子模型中,并保存在渲染引擎的布局树(Layout Tree)中。

优化:

Layout 阶段的计算量很大,因为它需要递归遍历 Render Tree 中的每个节点,并且需要根据每个节点的样式信息和布局信息计算出它们在屏幕上的位置和大小等属性。因此,在 Layout 阶段需要做出一些优化:

  1. 避免频繁地修改节点的样式信息,因为每次修改都会触发一次 Reflow,从而影响页面的渲染性能。
  2. 尽可能减少 Render Tree 的深度和宽度,避免过多的节点和嵌套结构,从而减少 Layout 的计算量。
  3. 将需要修改的节点尽可能的集中在一起进行修改,从而减少 Reflow 的次数。
  4. 使用 CSS 动画、过渡等方式,避免频繁修改节点的样式信息,从而减少 Reflow 的次数。

总之,在 Layout 阶段,需要尽可能减少节点的数量和复杂度,避免频繁地修改节点的样式信息,从而提高页面的渲染性能。

Paint

浏览器会根据 Layout Tree 中每个节点的布局盒子模型,将它们转换成屏幕上的像素点,并进行绘制,最终呈现在屏幕上。步骤如下:

  1. 遍历 Layout Tree,对于每个节点,根据它的布局盒子模型,将它们转换成屏幕上的像素点,并进行绘制。
  2. 对于每个节点的子节点,重复步骤1。
  3. 将所有绘制的结果合并成一张画布,呈现在屏幕上。

优化:

和Layout阶段类似,减少 repaint 的次数,且从最开的流程图中可以看出reflow必会引起repaint

优化总结

直到这里,会发现所有优化点都主要集中在生成DOM和CSSOM过程中,分为以下几点:

  1. JavaScript 文件放在 HTML 文件的底部最后加载;

  2. CSS样式表放到文档的头部;

  3. 减少回流(reflow)重绘(repaint)

    1. 减少DOM操作,如修改样式,且集中处理;
    2. 使用绝对定位、固定定位等方式,将需要修改的节点脱离文档流;
    3. 使用 CSS3 动画代替 JavaScript 动画;
  4. 减少html标签、css样式选择器的嵌套层级;

  5. 减少文件大小、个数从而使网络请求更快。

另外,优化的正确思路应该是:

  1. 先确定性能瓶颈
  2. 再做好性能规划
  3. 然后采取适当的优化策略

针对问题去优化