网页加载卡顿?试试 HTML 的新武器 blocking 属性!

发布于:2024-06-05 ⋅ 阅读:(162) ⋅ 点赞:(0)

大家好,我是前端宝哥。

691fc32d1b6adba3e9636a8763f91c1d.png

在网页开发的世界里,我们常常把渲染阻塞视作“洪水猛兽”,总想着如何避免它。随便一搜,就能发现大量关于消除渲染阻塞的技巧和建议。

但有趣的是,HTML 现在引入了一个名为 blocking 的新属性,它允许我们有意识地控制渲染的阻塞,直到特定的资源加载完毕。

通常,如果没有 asyncdefer 属性,<script> 标签会暂停 HTML 的解析,并且阻塞页面渲染,直到脚本被下载、解析和执行完毕。下面这个脚本标签,既是解析阻塞也是渲染阻塞的典型例子:

<head>
<script src="/script.js"></script>
</head>

那我们为什么需要这个新属性呢?原因有几点:

  • 它明确了阻塞的行为,清晰地传达了开发者的意图,这样团队成员在重构代码时,就不会无意中把它改成非阻塞的了。

  • 当与 deferasync 属性一起使用时,它可以阻塞渲染,同时不会阻塞 HTML 解析器的工作。

  • 与传统脚本不同,模块脚本默认是延迟加载的。<script type="module"> 现在可以通过 blocking 属性来实现渲染阻塞。

  • 通过 JavaScript 动态添加到 <head> 中的 <script><link><style> 元素,默认是不会阻塞渲染的。但现在,你可以灵活地让它们实现阻塞。

它是如何工作的?

blocking 属性可以被添加到 <head> 中的 <script><link><style> 元素上。截至目前,唯一能够被阻塞的是渲染过程(未来可能会扩展到其他操作)。

blocking="render" 用于标记那些在显示任何内容给用户之前必须加载完成的资源。在这些资源加载完成之前,浏览器窗口中不会绘制任何像素。

<script blocking="render" src="important.js" defer></script>

默认情况下,浏览器会为所有渲染阻塞的资源分配较高的优先级。但考虑到不是所有浏览器都支持 blocking 属性,添加 fetchpriority="high" 也是个不错的选择:

<script blocking="render" fetchpriority="high" src="important.js" defer></script>

这个属性同样适用于内联脚本。传统的内联脚本默认是阻塞的(deferasync 属性不适用于它们)。但是,如果你的脚本带有 type="module" 属性,即使是内联的,它也会延迟执行。

<script type="module" async blocking="render">
  // 重要的 JavaScript 代码...
</script>

内联模块脚本中的 async 属性意味着它会尽快执行。

让我们通过一个简单的例子来说明这一点。ChromaCheck 是一个展示浏览器支持的字体格式的网站。它使用客户端 JavaScript 实现,并且不会阻塞渲染:

在 JavaScript 有机会更新 DOM 之前,它会短暂地显示所有格式都不支持。如果 JavaScript 失败了,以一种中性的颜色开始渲染可能更安全,但你仍然会看到页面加载时样式的变化,这可能会让用户感到困惑。这个网站的整个设计都依赖于 JavaScript。依赖 JavaScript 的元素被放置在页面的顶部。脚本体积很小。在这种情况下,使用渲染阻塞实际上是一种改进。用户可能需要等待稍微长一点的时间才能看到第一个像素,但这是值得的。关键内容可以一次性完全渲染,避免了内容更新和重新排列带来的混乱。

<link> 标签中的 blocking="render"

你也可以在 <link> 元素上使用 blocking 属性。但如果你使用 <link> 配合 rel="preload" 来预加载资源,或者使用 rel="modulepreload" 来预加载 JavaScript 模块,那么 blocking 属性将不会生效。

样式表默认是阻塞渲染的——这是有充分理由的。这样可以防止无样式内容的闪烁(FOUC)。如果没有渲染阻塞,用户将只能看到浏览器的默认视觉样式——白色背景上的黑色 Times New Roman 等。然后,一旦样式表加载完成,页面的视图会发生巨大的变化,这并不是一个好的用户体验。例如,没有 CSS 的 theverge.com 看起来会是怎样:

由于样式表默认是阻塞的,所以在 <link><style> 标签中添加 blocking 属性几乎总是不必要的。唯一的例外是通过 JavaScript 动态加载样式表时。

下面这个样式表将不会阻塞渲染:

const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = './styles.css';
document.head.appendChild(link);

开发者通常会利用这种行为来有意识地延迟非关键 CSS 的加载。但是,如果你需要以这种方式加载关键 CSS(会改变整个页面布局或影响页面折叠内容的样式),则可以使用 blocking 属性:

const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = './styles.css';
link.setAttribute('blocking', 'render');
document.head.appendChild(link);

性能 API

如果你想验证 blocking 属性是否生效,可以在浏览器的开发者工具控制台中运行以下代码,来查看渲染阻塞资源的列表:

window.performance.getEntriesByType("resource")
  .filter(resource => resource.renderBlockingStatus === "blocking")
  .forEach(resource => console.log(resource.name));

渲染阻塞 DOM 节点

HTML 的渲染是逐步/增量进行的:

渲染可以在整个 HTML 文档被获取和解析之前就开始。

有一种方法可以根据特定 HTML 元素是否已解析来阻塞渲染。

<head> 中放置一个 <link>,其 href 属性引用所要元素的 id

<!DOCTYPE html>
<html>
  <head>
    <link rel="expect" href="#visually-critical-content" blocking="render">
  </head>
  <body>
    <header>...</header>
    <div id="visually-critical-content">...</div>
  </body>
</html>

这使得开发者可以更好地控制哪些内容包含在首次内容绘制 (FCP) 中。它应该只用于页面折叠之上的元素——否则你会不必要地延迟首次绘制。一旦元素被解析,页面就会对用户可见。如果找不到预期的元素,则在整个 HTML 文档解析完成时,渲染将被解除阻塞。

这个功能主要是在考虑跨文档视图转换的情况下添加到 Web 中的。

pagereveal 事件和视图转换

视图转换的初始实现只适用于客户端导航。blocking="render" 的一个主要用例是跨文档视图转换。考虑到这一点,Chrome/Edge 最近实现了一个新的 pagereveal 事件:

window.addEventListener('pagereveal', function(event) {
  console.log(event.viewTransition);
});

此事件监听器需要在渲染阻塞脚本中注册。否则,在 JavaScript 添加事件监听器时,该事件很可能已经发生,因此不会触发你的回调函数。pagereveal 在第一次渲染机会之前立即发生。这意味着在任何渲染阻塞 CSS 或 JavaScript 加载完成后,但它可能在所有 HTML 解析完成之前触发。但是,如果你在 <head> 中有 <link rel="expect" href="#thing" blocking="render">,你就可以使用 document.getElementById('thing') 查询该元素,而无需担心它为 null

请阅读 Eric Portis 关于此主题的有趣观点 视图转换破坏了增量渲染 以及浏览器工程师 Noam Rosenthal 的 回应。

用户实际看到了什么?

渲染阻塞会延迟将像素绘制到屏幕上,所以你可能会想,这是否意味着用户就得盯着空白页面看。在快速的网络连接下,这种情况很少见。过去,在浏览网页时,经常会在页面间看到白色的闪烁。为了解决这个问题,浏览器开始使用绘制保持(paint holding)。绘制保持会让用户停留在上一个页面,并显示一个加载指示器,直到新页面的首次内容绘制 (FCP) 准备就绪。如果你延迟了 FCP,用户就会在上一个页面上多停留一会儿。绘制保持只持续很短时间。如果你延迟了 FCP 太久,就会显示一个空白的白色页面。对于速度慢的网站,在慢速的 3G 网络上,这种白色空白的“闪烁”可能会持续很长时间。

浏览器支持

blocking 属性从版本 105 开始在 Chrome 和 Edge 中可用。它在 Samsung Internet 中也可用。请查看 MDN 获取最新的 浏览器兼容性数据。

通过 HTML 元素的 id 进行阻塞在 Chrome 124 中得到支持。

应该说,这个属性应该谨慎使用,因为它可能对首次内容绘制 (FCP) 产生负面影响,FCP 是一个重要的性能指标。浏览器有目的地阻塞渲染,直到样式表加载完成,这说明对于某些关键资源,渲染阻塞是正确的权衡。


往期推荐

Vue 小技巧:何时使用可组合函数

Vue.js表单开发宝藏工具集,让构建表单变得轻松又酷炫!

尤雨溪:Vue.js 十周年回顾与展望

Vue 单页面应用中,不要在 onMount 里添加事件监听器!

38个Vue、Nuxt 和 Vite 技巧、窍门和实践的合集

Vue 如何处理异步组件加载错误

Vue 3 响应式状态揭秘:ref() 函数的魔法

Vue 3 将推出新特性,可以抛弃虚拟DOM了!

如果我的分享对你有帮助,请:

1)点赞分享,防止以后找不到,想看的时候,在自己的朋友圈就能找到,很方便!

2)关注我,让我们一同成长!

谢谢你的支持!

每日分享前端干货,关注我加星标

以上,如果本文对你有所启发,关注我,点“d4b22f260804c7102f45f79e77613378.gif在看、点赞”支持下吧! 


网站公告

今日签到

点亮在社区的每一天
去签到