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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 之前写了两篇文章,涉及到了页面访问整个过程的一些分析,比如页面生命周期的介绍,页面访问时渲染过程中 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 的出现

相关文章
|
19天前
|
JavaScript 前端开发 Go
CSS 与 JS 对 DOM 解析和渲染的影响
【10月更文挑战第16天】CSS 和 JS 会在一定程度上影响 DOM 解析和渲染,了解它们之间的相互作用以及采取适当的优化措施是非常重要的。通过合理的布局和加载策略,可以提高网页的性能和用户体验,确保页面能够快速、流畅地呈现给用户。在实际开发中,要根据具体情况进行权衡和调整,以达到最佳的效果。
|
14天前
|
前端开发 UED 容器
在 CSS 中使用 Flex 布局实现页面自适应时需要注意什么?
【10月更文挑战第22天】在使用 Flex 布局实现页面自适应时,需要对其基本原理和特性有深入的理解,同时结合具体的布局需求和场景,进行细致的调整和优化。通过合理的设置和注意事项的把握,才能实现理想的自适应效果,提升用户体验。还可以根据实际情况进行更深入的探索和实践,以不断提升 Flex 布局的应用能力。
|
7天前
|
前端开发 JavaScript
如何在 JavaScript 中访问和修改 CSS 变量?
【10月更文挑战第28天】通过以上方法,可以在JavaScript中灵活地访问和修改CSS变量,从而实现根据用户交互、页面状态等动态地改变页面样式,为网页添加更多的交互性和动态效果。在实际应用中,可以根据具体的需求和场景选择合适的方法来操作CSS变量。
|
2天前
|
前端开发 JavaScript 安全
HTML+CSS+JS密码灯登录表单
通过结合使用HTML、CSS和JavaScript,我们创建了一个带有密码强度指示器的登录表单。这不仅提高了用户体验,还帮助用户创建更安全的密码。希望本文的详细介绍和代码示例能帮助您在实际项目中实现类似功能,提升网站的安全性和用户友好性。
9 3
|
7天前
|
前端开发 JavaScript UED
如何使用 JavaScript 动态修改 CSS 变量的值?
【10月更文挑战第28天】使用JavaScript动态修改CSS变量的值可以为页面带来更丰富的交互效果和动态样式变化,根据不同的应用场景和需求,可以选择合适的方法来实现CSS变量的动态修改,从而提高页面的灵活性和用户体验。
|
13天前
|
JSON 移动开发 数据格式
html5+css3+js移动端带歌词音乐播放器代码
音乐播放器特效是一款html5+css3+js制作的手机移动端音乐播放器代码,带歌词显示。包括支持单曲循环,歌词显示,歌曲搜索,音量控制,列表循环等功能。利用json获取音乐歌单和歌词,基于html5 audio属性手机音乐播放器代码。
64 6
|
14天前
|
前端开发
css页面顶部底部固定,中间自适应几种方法
【10月更文挑战第22天】css页面顶部底部固定,中间自适应几种方法
|
1月前
|
Web App开发 前端开发 JavaScript
JavaScript动态渲染页面爬取——Selenium的使用(一)
JavaScript动态渲染页面爬取——Selenium的使用(一)
|
1月前
|
编解码 前端开发 JavaScript
使用 CSS 打印样式为 Web 页面设置专业的打印机效果
使用 CSS 打印样式为 Web 页面设置专业的打印机效果
41 2
|
1月前
|
Web App开发 数据采集 JavaScript
JavaScript动态渲染页面爬取——Selenium的使用(二)
JavaScript动态渲染页面爬取——Selenium的使用(二)