HTML渲染过程

浏览从 url 输入到页面展示的过程:

  1. 判断是否存在缓存,有的直接读取,否则向服务器发送请求

  2. url 解析,根据 dns 系统进行 ip 查找

  3. 查找到IP之后,TCP 连接(三次握手)

  4. 传输数据,如果返回 301和302,重复上面步骤

  5. 返回 HTML

之后就是解析 HTML 内容

  1. 解析 DOM 元素生成 DOM 节点树;解析 CSS 样式,生成样式规则( CSSOM 树)

  2. DOM 树和 CSS 样式规则结合形成渲染树(Render Tree)

  3. Layout(回流):根据生成的渲染树,进行回流(Layout),得到的几何信息(位置、大小)

  4. Painting(重绘):根据渲染树及回流得到的几何信息,得到节点绝对像素

  5. Display:将像素发送给GPU,展示在页面上(这一步其实还有很多内容,比如会在GPU将多个合成层合并为同一个层,并展示在页面中。而css3硬件加速的原理则是新建合成层)

HTML 解析过程是通过浏览器的渲染进程进行处理的,每个标签页都是一个独立的渲染进程.
解析 HTML 的过程实质就是根据文档结构转换成可以表示文档结构的语法树的过程

TIP

需要着重指出的是,这是一个渐进的过程。为达到更好的用户体验,呈现引擎会力求尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,呈现引擎会将部分内容解析并显示出来。

布局重排, 回流是一个意思,只是不同浏览器的内核术语不同

HTML 解析

解析文档是指将文档转化成为有意义的结构,也就是可以让代码理解和使用的结构。解析得到的结果通常是代表了文档结构的节点树,它称作解析树或者语法树

示例: 解析 2 + 3 - 1 这个表达式,会返回下面的树:

解析大致过程

解析的过程可以分成两个子过程:词法分析和语法分析。

  • 词法分析

    词法分析(有时候也称为标记生成器)是将输入的内容分割成大量标记的过程。标记是语言中的词汇,即构成内容的单位。在人类的语言中,它相当于语言字典中的单词

  • 语法分析

    语法分析是应用语言的语法规则的过程,负责根据语言的语法规则分析文档的结构,从而构建解析树

解析是一个迭代的过程。通常,解析器会向词法分析器请求一个新标记,并尝试将其与某条语法规则进行匹配。如果发现了匹配规则,解析器会将一个对应于该标记的节点添加到解析树中,然后继续请求下一个标记。

如果没有规则可以匹配,解析器就会将标记存储到内部,并继续请求标记,直至找到可与所有内部存储的标记匹配的规则。如果找不到任何匹配规则,解析器就会引发一个异常。这意味着文档无效,包含语法错误

翻译

很多时候,解析树还不是最终产品。解析通常是在翻译过程中使用的,而翻译是指将输入文档转换成另一种格式。编译就是这样一个例子。编译器可将源代码编译成机器代码,具体过程是首先将源代码解析成解析树,然后将解析树翻译成机器代码文档。

DOM 解析

DOM树 的根节点是 Document 对象。

DOM 中的标记之间几乎是一一对应的关系。比如下面这段标记:

<html>
  <body>
    <p>
      Hello World
    </p>
    <div> <img src="example.png"/></div>
  </body>
</html>

可翻译成如下的 DOM 树:

所有的常规解析器都不适用于 HTML, 包括 XML 解析器,HTML 并不能很容易地用解析器所需的 与上下文无关的语法 来定义。原因如下:

  • 语言的宽容本质。

  • 浏览器历来对一些常见的无效 HTML 用法采取包容态度。

  • 解析过程需要不断地反复。源内容在解析过程中通常不会改变,但是在 HTML 中,脚本标记如果包含 document.write,就会添加额外的标记,这样解析过程实际上就更改了输入内容

因为 HTML 无法用常规的解析器进行解析,浏览器就根据HTM5规范创建了自定义的解析器来解析 HTML。

HTML 的正规格式:DTD(Document Type Definition,文档类型定义),但它无法构成与上下文无关的语法。

TIP

与上下文无关的语法

解析是以文档所遵循的语法规则(编写文档所用的语言或格式)为基础的。所有可以解析的格式都必须对应确定的语法(由词汇和语法规则构成),这称为与上下文无关的语法。粟子:若有一条规则: A->α, 则意味着A可能会在任何地方被 α 替换, 无论 A 在什么位置出现,平时说话经常会用代词指代东西, 代词就是上下文有关的, 不然无法判断其指代, 而某个名字往往就是上下文无关的, 这个名字直接被理解为其对应的事物

比如: 那个男人, 可以有多个理解, 需要上下文, 而书就明确被大脑接收并替换为"书"这种东西

标记化和树构建

HTML5 规范详细地描述了解析算法。此算法由两个阶段组成:标记化和树构建

  • 标记化是词法分析过程,将输入内容解析成多个标记。HTML 标记包括起始标记、结束标记、属性名称和属性值。

  • 标记生成器识别标记,传递给树构造器,然后接受下一个字符以识别下一个标记;如此反复直到输入的结束。

标记化算法

该算法的输出结果是 HTML 标记。该算法使用状态机来表示。每一个状态接收来自输入信息流的一个或多个字符,并根据这些字符更新下一个状态。当前的标记化状态和树结构状态会影响进入下一状态的决定。这意味着,即使接收的字符相同,对于下一个正确的状态也会产生不同的结果,具体取决于当前的状态

基本示例 - 将下面的 HTML 代码标记化:

<html>
  <body>
    Hello world
  </body>
</html>

初始状态是数据状态。遇到字符 < 时,状态更改为 标记打开状态。接收一个 a-z 字符会创建起始标记,状态更改为标记名称状态。这个状态会一直保持到接收 > 字符。在此期间接收的每个字符都会附加到新的标记名称上。在本例中,我们创建的标记是 html 标记。

遇到 > 标记时,会发送当前的标记,状态改回数据状态<body> 标记也会进行同样的处理。目前 htmlbody 标记均已发出。现在我们回到数据状态。接收到 Hello world 中的 H 字符时,将创建并发送字符标记,直到接收 </body> 中的 <。我们将为 Hello world 中的每个字符都发送一个字符标记。

现在我们回到标记打开状态。接收下一个输入字符 / 时,会创建 end tag token 并改为标记名称状态。我们会再次保持这个状态,直到接收 >。然后将发送新的标记,并回到“数据状态”。</html> 输入也会进行同样的处理。

树构建算法

在创建解析器的同时,也会创建 Document 对象。标记生成器发送的每个节点都会由树构建器进行处理,构建器接收到标记后会根据规范创建对应的DOM元素,这些元素不仅会添加到 DOM 树中,还会添加到开放元素的堆栈中。此堆栈用于纠正嵌套错误和处理未关闭的标记。其算法也可以用状态机来描述。这些状态称为插入模式

让我们来看看示例输入的树构建过程:

<html>
  <body>
    Hello world
  </body>
</html>

树构建阶段的输入是一个来自标记化阶段发出的标记

  1. 第一个模式是initial mode,接收HTML标记后转为before html模式,并在这个模式下重新处理此标记,这样会创建一个HTMLHtmlElement元素,并将其附加到Document根对象上

  2. 然后状态将改为before head,即使我们的示例中没有head标记,系统也会隐式创建一个 HTMLHeadElement,并将其添加到树中,然后进入in head模式,处理head中的元素后就转入after head模式

  3. 然后系统对 body 标记进行重新处理,创建并插入 HTMLBodyElement in body

  4. 现在,接收由Hello world字符串生成的一系列字符标记。接收第一个字符时会创建并插入Text节点,而其他字符也将附加到该节点

  5. 接收 body 结束标记会触发after body模式。现在我们将接收 HTML 结束标记,然后进入after after body模式。接收到文件结束标记后,解析过程就此结束。

解析结束后的操作

在此阶段,浏览器会将文档标注为交互状态,并开始解析那些处于 deferred 模式的脚本,也就是那些应在文档解析完成后才执行的脚本。然后,文档状态将设置为 完成,一个 加载 事件将随之触发。

TIP

**浏览器的容错机制:**您在浏览 HTML 网页时从来不会看到 语法无效 的错误。这是因为浏览器会纠正任何无效内容,然后继续工作

CSS解析

和 HTML 不同,CSS 是上下文无关的语法,可以由各种解析器进行解析

WebKit CSS 解析器

CSS解析器会将 CSS 文件解析成 StyleSheet 对象,且每个对象都包含 CSS 规则。CSS 规则对象则包含选择器和声明对象,以及其他与 CSS 语法对应的对象。

CSS样式与脚本的关系

当解析器遇到 <sciprt> 标记时会立即解析脚本并执行脚本,文档的解析将停止,直到脚脚本执行完毕。

如果脚本是使用的外链的形式加的,那么解析过程会停止,直到从网络同步抓取资源并解析然后执行后再继续

TIP

WebKit 和 Firefox 都进行了这项优化。在执行脚本时,其他线程会解析文档的其余部分,找出并加载需要通过网络加载的其他资源。通过这种方式,资源可以在并行连接上加载,从而提高总体速度。请注意,预解析器不会修改 DOM 树,而是将这项工作交由主解析器处理;预解析器只会解析外部资源(例如外部脚本、样式表和图片)的引用

生成渲染树

之后浏览器会使用DOM树和样式规则结合构建渲染树。渲染树是文档的可视化表示。它的作用是让浏览器能按照正确的顺序绘制内容
每一个渲染树都代表了一个矩形的区域,通常对应于相关节点的 CSS 框。它包含诸如宽度、高度和位置等几何信息。

图解:

为构建渲染树,浏览器主要完成了以下工作:

  1. 从DOM树的根节点开始遍历每个节点

  2. 对于每个见的节点,从 CSSOM 树中找到对应的样式规则,并应用它们

  3. 根据可见节点和对应的样式,组合生成渲染树

上面提到了可见节点,那么我就得知道怎么区别可见和不可见节点。比如不可见的节点包含

  • 一些不会渲染输出的节点,比如scriptlink

  • 通过CSS隐藏的节点。比如display:none。注意,利用visibilityopecity隐藏的节点,还是会显示在渲染树上。只有display:none的节点才不会显示在渲染树上。

TIP

再次注意渲染树是和 DOM 元素并非一一对应的,面试经常会问到这点

样式结合

构建呈现树时,需要计算每一个呈现对象的可视化属性。这是通过计算每个元素的样式属性来完成的。

样式表的来源包括浏览器的默认样式表、由网页作者提供的样式表以及由浏览器用户提供的用户样式表

样式计算存在以下难点:

  • 样式数据是一个超大的结构,存储了无数的样式属性,这可能造成内存问题

  • 如果不进行优化,为每一个元素查找匹配的规则会造成性能问题。要为每一个元素遍历整个规则列表来寻找匹配规则,这是一项浩大的工程。选择器会具有很复杂的结构,这就会导致某个匹配过程一开始看起来很可能是正确的,但最终发现其实是徒劳的,必须尝试其他匹配路径。

  • 应用规则涉及到相当复杂的层叠规则(用于定义这些规则的层次)。

规则分类

样式表解析完毕后,系统会根据选择器将 CSS 规则添加到某个哈希表中。这些哈希表的选择器各不相同,包括 ID、类名称、标记名称等,还有一种通用哈希表,适合不属于上述类别的规则。如果选择器是 ID,规则就会添加到 ID 表中;如果选择器是类,规则就会添加到类表中,依此类推。达到提高匹配效率的目的

我们以如下的样式规则为例:

p.error {color:red}
#messageDiv {height:50px}
div {margin:5px}

第一条规则将插入类表;第二条将插入 ID 表;而第三条将插入标记表。 对于下面的 HTML 代码段:

<p class="error">an error occurred </p>
<div id="messageDiv">this is a message</div>

我们首先会为 p 元素寻找匹配的规则。类表中有一个“error”键,在下面可以找到“p.error”的规则。div 元素在 ID 表(键为 ID)和标记表中有相关的规则。剩下的工作就是找出哪些根据键提取的规则是真正匹配的了。

从右往左解析

因为所有样式规则可能数量很大,而且绝大多数不会匹配到当前的 DOM 元素,如果从左到右匹配,当发现不匹配的情况时需要不断得进行回塑,这样会造成很多费时耗能,最后有很多都是无用的

eg:

<div>
   <div class="jartto">
      <p><span> 111 </span></p>
      <p><span> 222 </span></p>
      <p><span> 333 </span></p>
      <p><span class='yellow'> 444 </span></p>
   </div>
</div>
div > div.jartto p span.yellow{
   color:yellow;
}

对于上述例子,如果按从左到右的方式进行查找:

  1. 先找到所有 div 节点;

  2. div 节点内找到所有的子 div ,并且是 class = “jartto”

  3. 然后再依次匹配 pspan.yellow 等情况

  4. 遇到不匹配的情况,就必须回溯到一开始搜索的 div 或者 p 节点,然后去搜索下个节点,重复这样的过程

这样的搜索过程对于一个只是匹配很少节点的选择器来说,效率是极低的,因为我们花费了大量的时间在回溯匹配不符合规则的节点

如果换个思路,我们一开始过滤出跟目标节点最符合的集合出来,再在这个集合进行搜索,大大降低了搜索空间。来看看从右到左来解析选择器:

1.首先就查找到 span.yellow 的元素

2.紧接着我们判断这些节点中的前兄弟节点是否符合 P 这个规则,这样就又减少了集合的元素,只有符合当前的子规则才会匹配再上一条子规则

回流和重绘

回流

上文提到通 过DOM 节点和样式规则生成渲染树,可是我们还需要计算他们在视图(viewport)内的确切位置和大小,这个计算的阶段就是回流(重排)

HTML 采用基于流的布局模型,这意味着大多数情况下只要一次遍历就能计算出几何信息。处于流中靠后位置元素通常不会影响靠前位置元素的几何特征,因此布局可以按从左至右、从上至下的顺序遍历文档。但是也有例外情况,比如 HTML 表格的计算就需要不止一次的遍历

所有的呈现器都有一个 layout 或者 reflow 方法,每一个呈现器都会调用其需要进行布局的子代的 layout 方法

为了弄清楚每个对象在页面中的确切大不和位置,浏览器从渲染树的根节点开始遍历。看例子:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Critial Path: Hello world!</title>
  </head>
  <body>
    <div style="width: 50%">
      <div style="width: 50%">Hello world!</div>
    </div>
  </body>
</html>

我们可以看到,第一个 div 将节点的显示尺寸设置为视口宽度的50%,第二个 div 将其尺寸设置为父节点的50%。而在回流这个阶段,我们就需要根据视口具体的宽度,将其转为实际的像素值。(如下图)

重绘

我们通过渲染树和回流阶段,知道了哪些节点是可见的,并且知道了节点的样式、大小、位置信息等,那么我就可以将渲染树的各个节点都转换屏幕上的实际像素,这个阶段就是做重绘节点

在绘制阶段,系统会遍历呈现树,并调用呈现器的 paint 方法,将渲染树的内容显示在屏幕上

绘制顺序

  1. 背景颜色

  2. 背景图片

  3. 边框

  4. 子代

  5. 轮廓

既然知道了浏览器的渲染过程后,我们就来探讨下,何时会发生回流重绘。

何时发生回流重绘

在发生变化时,浏览器会尽可能做出最小的响应。因此,元素的颜色改变后,只会对该元素进行重绘。元素的位置改变后,只会对该元素及其子元素(可能还有同级元素)进行布局和重绘。添加 DOM 节点后,会对该节点进行布局和重绘。一些重大变化(例如增大“html”元素的字体)会导致缓存无效,使得整个呈现树都会进行重新布局和绘制。

从前面我们知道回流是计算节点的大小、位置等几何信息,那么当节点的布局几何信息发生变化的时候,就需要回流。比如以下情况

  • 添加或删除可见DOM节点

  • 节点尺寸发生了变化(包括外边距、内边框、边框大小、高度和宽度等)

  • 节点位置发生了变化

  • 节点内容发生了变化,比如文本变化或图片被另一个不同尺寸的图片所替代。

  • 页面一开始加载的时候

  • 浏览器窗口大小改变的时候(因为回流的根据视图大小来计算元素的位置和大小等几何信息的)

重绘是浏览器在元素的外观改变,但没有改变布局的情况下,需要重新绘制的过程。例如,改变元素的背景色,不会影响到元素的位置或大小,仅仅是外观的改变,这时就会发生重绘。

重绘会在以下情况下发生:

  • 改变元素的视觉外观,但不影响布局的属性,如改变颜色、阴影等

至此对浏览解析 HTML 的过程有了大概的了解

工作中的优化(注意点)

减少重排和重绘的触发

由于每次回流重绘都会造成额外的计算消耗,因此现代浏览器通过队列化修改并批量执行来优化重排过程。浏览器会将修改的操作放入到队列中,直到过一段时间或者到了一定的阈值,才清空队列。但是 当你获取布局信息的操作时候,会强制队列刷新 ,比如当你访问以下属性或者使用以下方法:

  • offsetTopoffsetLeftoffsetWidthoffsetHeight

  • scrollTopscrollLeftscrollWidthscrollHeight

  • clientTopclientLeftclientWidthclientHeight

  • getComputedStyle()

  • getBoundingClientRect

具体可以参考这个网站:https://gist.github.com/paulirish/5d52fb081b3570c81e3aopen in new window 所描述的方法

以上属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。因此,我们在修改样式的时候,最好避免频繁使用上面列出的属性,他们都会刷新渲染队列。 如果要使用它们,最好将值缓存起来

既然我们大概了解了回流和重绘的理论知识,也知道回流和重绘对浏览器渲染性能的影响,那接下就总结一下如何减少回流和重绘

合并回流和重绘的操作

由于回流和重绘可能造成比较昂贵的代价,因此最好减少它的发生次数。为了减少发生次数,我们可以合并多次对 DOM 和样式的修改,然后一次处理掉。考虑这个例子

const el = document.getElementById('test');
el.style.padding = '5px';
el.style.borderLeft = '1px';
el.style.borderRight = '2px';

例子中,有三个样式属性被修改了,每一个都会影响元素的几何结构,引起回流。当然,大部分现代浏览器都对其做了优化,因此,只会触发一次重排。但是如果在旧版的浏览器或者在上面代码执行的时候,有其他代码访问了布局信息(上文中的会触发回流的布局信息),那么就会导致三次重排。

因此,我们可以合并所有的改变然后依次处理,比如我们可以采取以下的方式

1. 使用 cssText

const el = document.getElementById('test');
el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';

2. 修改 CSS 的 class

const el = document.getElementById('test');
el.className += ' active';

批量修改 DOM 时使元素脱离文档流

当我们需要对 DOM 对一系列修改的时候,可以通过以下涌出减少回流重绘次数:

  1. 使元素脱离文档流

  2. 对其进行多次修改

  3. 将元素带回到文档中

该过程的第一步和第三步可能会引起回流,但是经过第一步,对 DOM 的所有修改都不会引起回流重绘,因为它已经不在渲染树了

有三种方式可以让 DOM 脱离文档流:

  • 隐藏元素,应用修改,重新显示

  • 使用文档片段(document fragment)在当前DOM之外构建一个子树,再把它拷贝因文档

  • 将原始元素拷贝到一个脱离文档的节点中,修改节点后,再替换原始的元素

eg. 考虑我们要执行一段批量插入节点的代码:

function appendDataToElement(appendToElement, data) {
    let li;
    for (let i = 0; i < data.length; i++) {
    	li = document.createElement('li');
        li.textContent = 'text';
        appendToElement.appendChild(li);
    }
}

const ul = document.getElementById('list');
appendDataToElement(ul, data);

如果我们直接这样执行的话,由于每次循环都会插入一个新的节点,会导致浏览器回流一次。

我们可以使用这三中方式进行优化:

1. 隐藏元素、应用修改、重新显示

这个会在节点隐藏和显示的时候,产生两次回流

function appendDataToElement(appendToElement, data) {
    let li;
    for (let i = 0; i < data.length; i++) {
    	li = document.createElement('li');
        li.textContent = 'text';
        appendToElement.appendChild(li);
    }
}
const ul = document.getElementById('list');
ul.style.display = 'none';
appendDataToElement(ul, data);
ul.style.display = 'block';

2. 使用文档片段(document fragment)在当前 DOM 之外构建一个子树,再把它拷贝回文档

const ul = document.getElementById('list');
const fragment = document.createDocumentFragment();
appendDataToElement(fragment, data);
ul.appendChild(fragment)

3. 将原始元素拷贝到一个脱离文档的节点中,修改节点后,再替换原始的元素。

const ul = document.getElementById('list');
const clone = ul.cloneNode(true);
appendDataToElement(clone, data);
ul.parentNode.replaceChild(clone, ul);

包括复杂动画效果,由于会经常的引起回流重绘,因此,我们可以使用绝对定位,让它脱离文档流。否则会引起父元素以及后续元素频繁的回流。

避免触发同步布局事件

上文我们说过,当我们访问元素的一些属性的时候,会导致浏览器强制清空队列,进行强制同步布局。举个例子,比如说我们想将一个 p 标签数组的宽度赋值为一个元素的宽度,我们可能写出这样的代码:

function initP() {
    for (let i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = box.offsetWidth + 'px';
    }
}

这段代码看上去是没有什么问题,可是其实会造成很大的性能问题。在每次循环的时候,都读取了 box 的一个 offsetWidth 属性值,然后利用它来更新 p 标签的 width 属性。这就导致了每一次循环的时候,浏览器都必须先使上一次循环中的样式更新操作生效,才能响应本次循环的样式读取操作。每一次循环都会强制浏览器刷新队列。我们可以优化为:

const width = box.offsetWidth;
function initP() {
    for (let i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = width + 'px';
    }
}

其它注意项

  • 避免使用 CSS 表达式,可能会引发回流

  • 避免使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局

  • 删除冗余的样式,提高构建呈现树阶段的样式匹配速度

css3硬件加速(GPU加速)

浏览器的GPU加速是指利用图形处理单元(GPU)而不是传统的中央处理单元(CPU)来渲染网页图形的技术。这项技术可以显著提高渲染性能,特别是在处理复杂的动画和视频处理时,因为GPU专为处理大量的并行任务而设计,这使得它在图形渲染方面比CPU更加高效。

GPU加速的工作原理

当启用GPU加速时,浏览器会将部分渲染任务,如CSS动画、画布绘制(Canvas)和WebGL内容,转移到GPU上执行。这是通过将这些任务包装成纹理和其它图形操作,然后发送给GPU处理。完成这些任务后,GPU生成的输出(即渲染好的图像)会被传送回浏览器显示。

触发GPU加速的情况

并不是所有的CSS属性更改或动画都会触发GPU加速。通常,以下操作可能会触发GPU加速:

  • 使用CSS3D变换,如translate3d、scale3d或rotate3d。

  • 使用CSS过渡(transitions)和动画(animations)涉及到透明度(opacity)和变形(transform)。

  • 使用WebGL技术绘制3D图形。

  • 启用某些CSS滤镜,如blur或drop-shadow。

如何启用GPU加速

在大多数现代浏览器中,GPU加速是默认启用的。但是,开发者可以通过特定的CSS技巧显式触发GPU加速,例如,即使不改变元素的实际位置或形状,也可以通过在元素上应用transform: translateZ(0);或transform: translate3d(0,0,0);来“欺骗”浏览器使用GPU加速。

css3硬件加速的坑:

  • 如果你为太多元素使用 css3 硬件加速,会导致内存占用较大,会有性能问题

  • 在GPU渲染字体会导致抗锯齿无效。这是因为 GPU 和 CPU 的算法不同。因此如果你不在动画结束的时候关闭硬件加速,会产生字体模糊。(后面会说如果避免这个问题)

避免阻塞

首先渲染的前提是生成渲染树,所以 HTML解析 和 CSS解析 肯定会阻塞渲染。如果你想渲染的越快,你越应该降低一开始需要渲染的文件大小,并且扁平层级,优化选择器

其次当浏览器在解析到 script 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在就 DOM 元素之前或之间加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因

TIP

当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性

关于阻塞还需要注意以下几点:

CSS加载会阻塞 DOM 解析吗?

CSS加载本身不会阻塞DOM(文档对象模型)的解析,但会阻塞DOM的渲染以及阻塞后续的JavaScript执行,直到CSSOM(CSS对象模型)构建完成。这是因为浏览器需要确保在执行JavaScript和渲染页面前,页面的样式是最终确定的,以避免出现样式的闪烁或重绘

CSS加载会阻塞 DOM 渲染吗?

DOM 渲染需要 css tree + dom tree = render tree 所以 CSS 加载会阻塞 DOM 树渲染

css加载会阻塞后面js语句的执行嘛?

如果JavaScript脚本通过 <script> 标签添加到HTML中,并且没有设置 asyncdefer 属性,那么浏览器在执行这些脚本之前会等待CSSOM的构建完成。这是因为JavaScript可能会操作依赖于CSSOM的样式信息,如计算的样式

Q&A

关于使用CSS3硬件加速,导致字体模糊

总结来说就是使用 transform 时,translatescalerotate 属性的值应该尽量为整数,如果是非整数的话会导致字体模糊

dom树节点和渲染树节点一一对应吗

根据之前上图的渲染图未所以发现 dom 树节点和渲染树节点并不是一一对应的,比如 display:node 的元素将不会出现在渲染树中

CSS 如何阻塞文档解析?

  • CSS 加载不会阻塞 DOM的解析(DOM树的构建)
  • CSS 加载会阻塞 DOM 的渲染
  • CSS 加载会阻塞 JS 脚本执行

cssom 树的构建过程与 dom 树的构建过程是否会彼此阻塞

CSS树(CSSOM树)的构建过程与DOM树的构建过程是两个独立但相互关联的过程。它们不会直接彼此阻塞,但有着密切的关系,特别是在渲染页面时。

浏览器的工作原理:新式网络浏览器幕后揭秘open in new window
css加载会造成阻塞吗open in new window
浏览器的工作原理:新式网络浏览器幕后揭秘open in new window
探究 CSS 解析原理open in new window

Last Updated:
Contributors: 156081289@qq.com