浏览器渲染解析HTML过程详解(CSS匹配规则为何从右到左!)
发布日期:2021-06-30 18:25:18 浏览次数:3 分类:技术文章

本文共 4422 字,大约阅读时间需要 14 分钟。

在这里插入图片描述

浏览器从接收到服务器返回的 HTML 到渲染像素到屏幕上,中间经历了很多的步骤。浏览器初次绘制网页的必经过程称之为“关键渲染路径(Critical Rendering Path,以下称CRP)”。指的是浏览器从请求HTML,CSS,JavaScript 文件开始,到将它们最终以像素输出到屏幕上这一过程。包括以下几个部分:

1.构建DOM

■将HTML解析成许多Tokens

■将Tokens解析成object

■将object组合成为一个DOM树

2.构建CSSOM

■解析CSS文件,并构建出一个CSSOM树(过程类似于DOM构建)

3.构建Render Tree

■结合DOM和CSSOM构建出一颗Render树

4. Layout

■计算出元素相对于viewport的相对位置

5. Paint

■将render tree转换成像素,显示在屏幕上

下面就是每个步骤具体的实现办法

值得注意的是,上面的过程并不是依次进行的,而是存在一定交叉,后面会详细解释。

想要提高网页加载速度,提升用户体验,就需要在第一次加载时让重要的元素尽快显示在屏幕上。而不是等所有元素全部准备就绪再显示,下面一幅图说明了这两种方式的差异。

在这里插入图片描述

构建DOM树

DOM (Document Object Model,文档对象模型,构建DOM是必不可少的一环,浏览器从发出请求开始到得到HTML文件后,第一件事就是将HTML解析成许多Tokens,再将Tokens转换成object,最后将object组合成一颗DOM树。

这个过程是循序渐进的,我们假设HTML文件很大,一个RTT(Round-Trip Time),往返时延只能得到一部分,浏览器得到这部分之后就会开始构建DOM,并不会等到整个文档就位才开始渲染。这样做可以加快构建过程,而且由于自顶向下构建,后面的构建不会对前面造成影响。而后面我们将会看到,CSSOM则必须等到所有字节收到才开始构建

DOM (Document Object Model,文档对象模型,构建DOM是必不可少的一环,浏览器从发出请求开始到得到HTML文件后,第一件事就是将HTML解析成许多Tokens,再将Tokens转换成object,最后将object组合成一颗DOM树。

这个过程是循序渐进的,我们假设HTML文件很大,一个RTT (Round-Trip Time),往返时延只能得到一部分,浏览器得到这部分之后就会开始构建DOM,并不会等到整个文档就位才开始渲染。这样做可以加快构建过程,而且由于自顶向下构建,后面的构建不会对前面造成影响。而后面我们将会看到,CSSOM则必须等到所有字节收到才开始构建。

构建CSSOM

CSSOM (CSS Object Model,CSS对象模型,构建过程类似DOM,当HTML解析中遇到< link >标签时,会请求对应的CSS文件,当CSS文件就位时便开始解析它(如果遇到行内< style >时则直接解析),这一解析过程可以和构建DOM同时进行。

构建出来的CSSOM是这样的:

在这里插入图片描述

需要注意的是,上面并不是一颗完整的CSSOM树,文档有一些默认的CSS样式,称作user agent styles,上面只展示了我们覆盖的部分。

如果是外部样式,CSSOM的构建必须要获得一份完整的CSS文件,而不像DOM的构建是一个循序渐进的过程。因为CSS文件中包含大量的样式,后面的样式会覆盖前面的样式,如果我们提前就构建CSSOM,可能会得到错误的结果。

这一部分对应Dev Tool Performance Panel里的过程如下:

1.如果是内联样式,CSSOM构建包含在Parse HTML过程中

2.如果是外部样式,包含在Parse Stylesheer过程中

3.如果没有设置样式,使用User Agent Style,则包含在Parse HTML过程中

构建Render Tree

这也是关键的一步,浏览器使用 DOM 和 CSSOM 构建出 Render Tree。此时不像构建 DOM 一样把所有节点构建出来,浏览器只构建需要在屏幕上显示的部分,因此像< head>,< meta>这些标签就无需构建了。同时,对于 display: none的元素,也无需构建。

display: none告诉浏览器这个元素无需出现在 Render Tree 中,但是 visibility: hidden只是隐藏了这个元素,但是元素还占空间,会影响到后面的 Layout,因此仍然需要出现在 Render Tree 中。

在这里插入图片描述

构建过程遵循以下步骤:

  1. 浏览器从 DOM 树开始,遍历每一个可见“节点。

  2. 对于每一个“可见“节点,在 CSSON 上找到匹配的样式并应用。

  3. 生成 Render Tree。

Layout

我们现在为止已经得到了所有元素的自身信息,但是还不知道它们相对于 Viewport 的位置和大小,Layout 这过程需要计算的就是这两个信息。

根据这两个信息,Layout 输出元素的 Box Model(盒模型),关于这个,我们可以去了解文档流 ( Document Flow)和视格式化模型(CSS Visual Formatting Model)。

这里的 Layout 阶段和 Chrome Dev Toll 中的 Layout过程是不同的。根据官网文档的解释,Layout 过程捕捉了 Render Tree 构建和 Layout 部分

目前为止,我们已经拿到了元素相对于 Viewport 的详细信息,所有的值都已经计算为相对 Viewport 的精确像素大小和位置,就差显示了

Paint

浏览器将每一个节点以像素显示在屏幕上,最终我们看到页面

这一过程需要的时间与文档大小、CSS 应用样式的数量和复杂度、设备自身都有关,例如对简单的颜色进行

Paint 是简单的,但是 box- shadow 进行 paint!则是复杂的

这一部分对应 Dev Tool Performance Panel 里* Paint*过程

引入JavaScript

前面的过程都没有提到 Javascript,但在如今,Javascript 却是网页中不可缺的一部分。这里对它如何影响 CRP 做一个概要,具体细节我们可以使用 Chrome Dev Tools 进行了测验。

  1. 解析 HTML 构建 DOM 时,遇到 Javascript 会被阻塞

  2. Javascript 执行会被 CSSOM 构建阻塞,也就是说,Javascript 必须等到 CSSOM 构建完成后才会执行

  3. 如果使用异步脚本,脚本的网先降低,且网络请求期间不阻塞 DOM 构建,直到请求完成才开始执行脚本

我们知道 DOM 几乎都是在 CSSO 构建完成前就构建完成了,而引入 JS 后,DOM 构建被 JS 执行打断,而 js 执行又必须等 CSSOM 构建完毕,这无疑延长了第一次 CRP 时间,让页面首次出现画面的时间更长

而且浏览器在解析到< ink >, < img >等等标签时,会马上发出 HTTP 请求,而且解析也将继续进行,解析完成后会触发 readystatechange 事件和 Domcontentloaded 事件,由于时间间隔已经到了 100 微秒级别,事件间隔有些许差异。

从这里我们可以看出,DOM 即使构建完成,也需要等 CSSOM 构建完成,才能经过一个完整的 CRP 并呈现画面,因此为了画面尽快呈现,我们需要尽早构建出 CSSOM,比如:

  1. Html 文档中的< style >或者< link >标签应该放在< head >里并尽早发现被解析
  2. 减少第一次请求的 CSS 文件大小
  3. 甚至可以将最重要部分的 CSS Rule 以< style >标签发在< head >里,无需网络请求

Async Script

如果 Script?会对页面首次渲染造成这么大的影响,有没有什么好的办法解決它呢?

答案是肯定的,就是使用异步脚本 < script src=" " async />

使用异步脚本,其实就是告诉浏览器几件事

  1. 无需阻塞 DOM,在对 Scripti 执行网络请求期间可以继续构建 DOM,直到拿到 Script。之后再去执行

  2. 将该 Script 的网络请求优先级降低,延长响应时间

需要注意如下几点:

  1. 异步脚本是网络请求期间不阻塞 DOM,拿到脚本之后马上执行,执行时还是会阻塞 DOM,但是由于响应时间被延长,此时往往 DOM 已经构建完毕(下面的测验图片将会看到,CSSOM 也已经构建完毕而且页面很快就发生第一次渲染),异步脚本的执行发生在第一次渲染之后

  2. 只有外部脚本可以使用 async?关键字变成异步,而且注意其与延迟脚本(< script defer >)的区别,后者是在 Document?被解析完毕而 Domcontentloadede 事件触发之前执行,前者则是在下载完毕后执行。

  3. 对于使用 document, createElement 创建的< script >,默认就是异步脚本。

最后就是css的匹配规则

相信大多数初学者都会认为 CSS 匹配是左向右的,其实恰恰相反。学习了 CRP,也就不难理解为什么了

CSS 匹配发生在 Render Tree 构建时(Chrome Dev Toolsy 将其归属于layout 过程)。此时浏览器构建出了 DOM,而且拿到了 CSS 样式,此时要做的就是把样式跟 DOM 上的节点对应上,浏览器为了提高性能需要做的就是快速匹配。

首先要明确一点,浏览器此时是给一个“可见“节点找对应的规则,这和 jQuery 选择器不同,后者是使用一个规则去找对应的节点,这样从左到右或许更快。但是对于前者,由于 CSS 的庞大,一个 CSS 文件中或许有上千条规则,而且对于当前节点来说,大多数规则是匹配不上的,到此为止,稍微想一下就知道,如果从右开始匹配(也是从更精确的位置开始),能更快排除不合适的大部分节点,而如果从左开始,只有深入了会发现匹配失败,如果大部分规则层级都比较深,就比较浪费资源了。

除了上面这点,我们前面还提到 DOM 构建是“循序渐进的”,而且 DOM 不阳塞 Render Tree 构建(只有 CSSOM 阻塞),这样也是为了能让页面更早有元素星现。考虑如下情況,如果我们此时构建的只是部分 DOM,而 CSSOM 构建完成,浏览器就会构建 Render Tree。这个时候对每一个节点,如果找到一条规则从右向左匹配我们只需要逐层观察该节点父节点是否匹配,而此时其父节点肯定已经在 DOM 上。但是反过来,我们可能会匹配到一个 DOM 上尚未存在的节点,此时的匹配过程就浪费了资源。

转载地址:https://lichen.blog.csdn.net/article/details/114591401 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:nodejs低版本升级成高版本
下一篇:使用脚手架快速构建egg项目(绝对有效!!)

发表评论

最新留言

不错!
[***.144.177.141]2024年04月28日 17时27分36秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章