页面加载时会被 JS 和 CSS 阻塞吗?

简介: 之前写了两篇文章,涉及到了页面访问整个过程的一些分析,比如页面生命周期的介绍,页面访问时渲染过程中 HTML、JS 的关系,前面两篇只是抓住了 JS,没有囊括 CSS,并且在复现上没

之前写了两篇文章,涉及到了页面访问整个过程的一些分析,比如页面生命周期的介绍页面访问时渲染过程中 HTML、JS 的关系,前面两篇只是抓住了 JS,没有囊括 CSS,并且在复现上没有明确给出工具,而今天这篇文章将使用 Chrome 的 Network 和 Performance 工具去分析整个页面的加载过程

  1. Network: 分析请求,文章中用于分析请求发送时序关系
  2. Performance: 分析页面加载性能,用于查看 HTML, CSS, JS 解析时序

前排提示,使用上面两种工具去分析时,请打开无痕模式并且关掉在无痕模式允许运行的插件,目的是为了避免插件脚本的加载影响后期的分析

CSS

就目前类似的文章中,都是在头部导入采用的延时去模拟 CSS 可能会阻塞 DOM 解析的效果,对于 CSS 这类静态资源来说,它们是由专门的下载线程来下载的,不会阻塞 GUI 线程(HTML 解析所在线程),但是 <link><script> 是同步请求,就是需要发送请求后需要等待响应,那么阻塞 HTML 的真正原因可能是 1. 同步请求因为延迟导致的阻塞,等返回响应后 HTML 就会解析 2. HTML 遇到头部的外联 CSS 样式后,文件太大导致的阻塞

为了搞清楚这两个点,我分别设置了延时和大 CSS 文件两种模拟方式,代码如下

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="http://localhost:8000/500.css" />
  </head>

  <body>
    <p id="test">究竟什么时候解析到我呢?</p>
  </body>
</html>
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="http://localhost:8000/big.css" />
  </head>

  <body>
    <p id="test">究竟什么时候解析到我呢?</p>
  </body>
</html>

延时模式下

img4.png

大文件模式下

img3.png

仔细看上面两个图蓝色虚线,也就是 DCL 所在的位置,全部都是在 CSS 加载开始一点点就完成了,对于上面的假设

  1. 同步请求阻塞
  2. 大文件阻塞

都没有影响到 HTML 的解析(注:外联样式 CSS 的请求实际上是在 Parse HTML 之前发生的)

由上面我们也可以知道,延时阻塞和大文件阻塞起到的效果是一致的,因此接下来的 demo 中都会采用延时的方案

由上我们可以知道,位于头部(头部一般指 <head> 中) CSS 不会阻塞 DOM 解析(头部是一个伏笔, 先不讲)

位于头部的 CSS 会阻塞页面渲染

页面加载的过程中实际上是一行行读取 HTML,因此页面的内容也是一点点的打印/渲染出来,所以就有了 First Plain 的出现,含义为页面从开始加载到页面内容的任何部分在屏幕上完成渲染的时间,以今日头条的访问为例,当页面还在转圈时,页面内容其实就已经能够被看到大部分了,之所以还在转圈是因为图片等资源还没有下载完成,而根据白屏时间的定义不就是 First Plain 之前的时间吗?

CSS 阻塞页面渲染,为了避免页面重复渲染(比如 红 -> 绿 -> 白 -> ...),页面会等待 CSS 的下载完成,前面测试了延时和大文件 CSS 的加载,两张图中红色部分为 load,绿色为 First Plain,即页面第一次渲染,由此可见在头部的 CSS 阻塞页面的渲染,不会阻塞 DOM

凡事有例外 - FOUC

<link> 被置于某些元素的中间,就会阻塞 dom 的解析了,看看下面的例子

<!DOCTYPE html>
<html>
  <body>
    <p>究竟什么时候解析到我呢?</p>
  </body>
  <!-- 延时 500ms 发送 CSS 文件 -->
  <link rel="stylesheet" href="http://localhost:8000/500.css" />
</html>

结果如下

img5.png

震惊!DOMContentLoaded 竟然在 Load 的后面(红线是 Load,蓝线是 DOMContentLoaded),至少在 Chrome 的 Performance 上给出了我们这样的答案

那么对于 HTML 的解析呢?看下图

img6.png

img7.png

HTML 解析的间隔时间出现了断层,也就是 CSS 的加载已经阻塞了 HTML 的解析,也就是 DOM 的解析,不同于在 <head> 标签的情况,DOMContentLoaded 需要等待 CSS 的完全加载,对于类似文章的观点,CSS 不会阻塞 DOM 的解析,在这个例子当中是错误的

也就是说,存在特例,<head>尾部的 CSS 会阻塞位于其后紧跟的 DOM 的解析

这种情况被称为 FOUC(Flash of Unstyled Content), 样式闪烁, 因为 First Plain 机制的存在, 会将已经解析的 DOM 和 CSSOM 结合生成渲染树并进行部分渲染, 当该 CSS 加载完成后, 才会继续解析 DOM, 而且之前已经渲染的内容将会根据解析完成的 CSS 进行重绘

注意, FOUC 在不同的浏览器下可能会有不同的表现, Firefox, Chrome, Edge 表现一致, 如果要考虑其他浏览器其自行测试

CSS 部分 - 结论

页面加载 - 以展现内容的时机为基准

  1. CSS 放在头部加载 - 会阻塞页面加载, 页面内容会等待 CSS 的加载完成, 即使它需要加载 10s
  2. CSS 放在尾部加载 - 不会阻塞页面加载, 因为 First Plain 机制, CSS 前的已经被解析页面内容的会被渲染, 但是存在重绘

DOM 解析 - 以触发 DOMContentLoaded 的时机为基准

  1. CSS 放在头部加载 - 不会阻塞 DOM 解析
  2. CSS 放在尾部加载 - 会直接阻塞 DOM 解析

此时 CSS 特指外联样式表 <link rel="stylesheet" href="xxx">

JS 和 CSS

JS 的确阻塞 DOM 的解析,详细的内容可以看这篇文章,页面访问时渲染过程中 HTML、JS 的关系,那如果是 JS 和 CSS 一起呢?上面的分析给出了外联样式 CSS 放在头部和尾部是有不同的情况的,而没有 CSS 的情况下,把 JS 放在 HTML 任意部分都会阻塞 DOM 的解析,那么 CSS 混合 JS 就可以有以下 4 种情况

  1. 头部中引入外联样式 <head>...<link />...</head>

    1. JS 在 CSS 前 <head>...<script></script>...<link />...</head>
    2. JS 在 CSS 后

      1. <head>...<link />...<script></script>...</head>
      2. <head>...<link />...</head>......<script></script>
  2. 尾部中引入外联样式 <body></body>...<link />

    1. JS 在 CSS 前 <body></body>...<link />...<script></script>
    2. JS 在 CSS 前 <body></body>...<script></script>...<link />

例子如下

<!-- 1 -->
<!DOCTYPE html>
<html>
  <head>
    <script>
      console.log(document.querySelector("p"));
    </script>
    <link rel="stylesheet" href="http://localhost:8000/big.css" />
  </head>

  <body>
    <p id="test">究竟什么时候解析到我呢?</p>
  </body>
</html>

第一种情况,JS 会阻塞 DOM 的解析,先执行 <scirpt> 的内容,无需等待 CSS 加载

<!-- 2 -->
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="http://localhost:8000/big.css" />
    <script>
      console.log(document.querySelector("p"));
    </script>
  </head>

  <body>
    <p id="test">究竟什么时候解析到我呢?</p>
  </body>
</html>

第二种情况,JS 需要等待 CSS 的加载完成才能够执行,因为 JS 可能需要获取到 CSS 中设置好的样式属性,比如宽度和高度等,那么后面的 DOM 就无法解析了,因此 CSS 加载 -> JS 等待 CSS 加载 -> JS 阻塞 DOM

img8.png

注意,补充一个小细节,看看下面这个例子

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="http://localhost:8000/big.css" />
  </head>
  <body>
    <p id="test">究竟什么时候解析到我呢?</p>
  </body>
  <script>
    console.log(document.querySelector("p"));
  </script>
</html>

同样是 CSS 在 JS 前,head 中的 JS 是获取不到 <p> 的,但 <body> 下面的是能获取到的,证明虽然 DOM 的解析被阻塞了,但是已经被解析出来的 DOM 是不影响操作的

又是一个小细节

<!-- 3 -->
<!DOCTYPE html>
<html>
  <body>
    <p id="test">究竟什么时候解析到我呢?</p>
  </body>
  <script>
    console.log(document.querySelector("p"));
  </script>
  <link rel="stylesheet" href="http://localhost:8000/500.css" />
</html>

第三种情况,同第一种情况,无需等待 CSS 的加载,但是 DOMCOntentLoaded 需要等待 CSS 的加载

<!-- 4 -->
<!DOCTYPE html>
<html>
  <body>
    <p id="test">究竟什么时候解析到我呢?</p>
  </body>
  <link rel="stylesheet" href="http://localhost:8000/500.css" />
  <script>
    console.log(document.querySelector("p"));
  </script>
</html>

第四种情况,同第二种情况,需等待 CSS 的加载,但是 DOMCOntentLoaded 需要等待 CSS 的加载

因此 DOMContentLoaded 在 MDN 上的定义在实际中并不一定准确, 或者说语义上有点误解, DOMContentLoaded 的触发会因为 JS 的位置决定是否需要等待 CSS 的加载

CSS 的加载在 JS 前则会延迟 DOMContentLoaded, 原因是 CSS 的加载将会阻塞 JS 的执行, 而 DOMContentLoaded 的触发需要等待 HTML 的内联 JS 和外联 JS 的加载执行完成, 因此 CSS 间接延迟了 DOMContentLoaded 的触发

在 Firefox, Chrome, Edge 中,如果 <link rel="stylesheet" href="xxx"> 后面跟着 <script>, 则 CSS 加载完成后, 才能触发 DOMContentLoaded

JS 和 CSS 部分结论

  1. CSS 阻塞 JS 执行
  2. CSS 间接阻塞 DOMContentLoaded, 因为 JS 需要等待 CSS 加载
  3. JS 会阻塞 DOMContentLoaded 触发时间, 但不影响其操作在其之前的 DOM
  4. 页面加载(以展现内容的时机为基准), JS 不会阻塞在其之前的 HTML 内容的渲染

总结

对于 JS ,在交互上,想要正确的操作 DOM,将 JS 放在 HTML 尾部,当解析到 JS 时,虽然没有触发 DOMCOntentLoaded,但是已经可以正确操作 DOM,减少加载时间,将 JS 异步化,对于外部 JS,采用添加 defer 属性或者 async 引入

而 CSS 在头部不会阻塞 DOM 的解析,但是跟在其后的 JS 在 CSS 加载的这段时间是不能够被执行的, 即使 JS 下载完成时间比 CSS 要快, 优先加载 CSS 避免 FOUC 的出现

相关文章
|
4月前
|
前端开发 算法 Java
【CSS】前端三大件之一,如何学好?从基本用法开始吧!(二):CSS伪类:UI伪类、结构化伪类;通过伪类获得子元素的第n个元素;创建一个伪元素展示在页面中;获得最后一个元素;处理聚焦元素的样式
伪类:伪类这个叫法源自于它们跟类相似,但实际上并没有类会附加到标记中的标签上。 伪类分为两种(以及新增的伪类选择器): UI伪类:会在HTML元素处于某种状态时(例如:鼠标指针位于连接上),为该元素应用CSS样式。 :hover 结构化伪类:会在标记中存在某种结构上的关系时 例如: 某元素是一组元素中的第一个或最后一个,为该元素应用CSS样式。 :not和:target(CSS3新增的两个特殊的伪类选择器)
254 1
|
8月前
|
移动开发 前端开发 JavaScript
征信报告修改器,征信报告生成器,制作软件无痕修改软件【js+html+css】
本项目为信用评分模拟器教学工具,采用HTML5实现,仅供学习参考。核心功能通过JavaScript构建,包含虚拟数据生成、权重分配及信用因素分析(如还款记录、信用使用率等)。
|
8月前
|
前端开发 JavaScript
个人征信电子版无痕修改, 个人信用报告pdf修改,js+html+css即可实现【仅供学习用途】
本代码展示了一个信用知识学习系统的前端实现,包含评分计算、因素分析和建议生成功能。所有数据均为模拟生成
|
5月前
|
编解码 前端开发 JavaScript
js react antd 实现页面低分变率和高分变率下字体大小自适用,主要是配置antd
在React中结合Ant Design与媒体查询,通过less变量和响应式断点动态调整`@font-size-base`,实现多分辨率下字体自适应,提升跨设备体验。
244 2
|
8月前
|
存储 前端开发 JavaScript
仿真银行app下载安装, 银行卡虚拟余额制作app,用html+css+js实现逼真娱乐工具
这是一个简单的银行账户模拟器项目,用于学习前端开发基础。用户可进行存款、取款操作,所有数据存储于浏览器内存中
|
8月前
|
前端开发
个人征信PDF无痕修改软件,个人征信模板可编辑,个人征信报告p图神器【js+html+css仅供学习用途】
这是一款信用知识学习系统,旨在帮助用户了解征信基本概念、信用评分计算原理及信用行为影响。系统通过模拟数据生成信用报告,涵盖还款记录
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
326 2
|
JavaScript 前端开发
JavaScript中的原型 保姆级文章一文搞懂
本文详细解析了JavaScript中的原型概念,从构造函数、原型对象、`__proto__`属性、`constructor`属性到原型链,层层递进地解释了JavaScript如何通过原型实现继承机制。适合初学者深入理解JS面向对象编程的核心原理。
265 1
JavaScript中的原型 保姆级文章一文搞懂
JS+CSS3文章内容背景黑白切换源码
JS+CSS3文章内容背景黑白切换源码是一款基于JS+CSS3制作的简单网页文章文字内容背景颜色黑白切换效果。
165 0
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
474 5