关键请求 | CSS-Tricks - 众成翻译

1,335 阅读11分钟
原文链接: www.zcfy.cc

网站服务看起来很简单:发送HTML,浏览器进行处理并加载资源,然后我们只要耐心的等页面渲染好即可。

但是你很少知道,整个过程背后发生了很多事情。

你有没有想过,浏览器如何知道应该加载哪些资源,并且以什么顺序来加载这些资源?

今天我们将看看如何利用资源优先级来提高加载速度。

页面资源的加载流程

大多数浏览器使用流解析器来解析HTML,解析器通过标记(markup)发现外链的资源,随后这些资源会根据一个预先确定的优先级被添加到一个网络队列中。

在Chrome中,资源有以下几种优先级:很低[Very Low],低[Low] ,中[Medium],高[High]和非常高[Very high]。在Chrome DevTools的源码中,定义了稍微不同的叫法: 最低[Lowest],低[Low], 中[Medium], 高[High]和最高[Highest].

如果你想看你网站请求的优先级,你可以通过在Chrome DevTools的网络请求面板中开启Priority栏来查看。

提示:如果你使用Safari Technology preview,用同样的方式也能看到(新的)Prority栏。

通过右键单击任意请求面板标签来显示请求的优先级。

你还可以在Performance标签下查看每个请求的优先级。

鼠标移入时显示资源的加载时间和优先级。

Chrome如何定义资源的优先级?

每种资源类型(CSS、JavaScript、字体等)都有自己的一套规则,规定它们将如何被排序。下面是关于网络优先级计划的不完全清单:

HTML— 最高优先级(Highest)

Styles—最高优先级(Highest)。 使用@import引用的样式同样也是最高优先级,但是会被排在阻塞脚本之后。

Images是唯一可以根据视口动态改变优先级的资源。所有图像以低优先级(Low)开始,但在可见视口中渲染时将被提升为中等优先级(Medium)。视口之外的图像(也称为“below the fold”)依旧保持低优先级。

在这篇文章的调研阶段,(在Paul Irish的帮助下)我发现Chrome DevTools将已被提升为中优先级的图片误报为低优先级,Paul写过一个错误报告,你可以在这里进行查看

如果你想要知道Chrome源码里是如何进行图片优先级提升的,你可以从UpdateAllImageResourcePrioritiesComputeResourcePriority这开始看。

Ajax/XHR/fetch()—高优先级(High)。

Script 遵循一个复杂的加载优先级方案。(Jake Archibald在2013年详细描述了这个方案。如果你想了解它背后的科学,我建议你深入地研究下)。简单概括一下是这样的:

  • 使用<script src="name.js"></script>加载的script,如果该标签出现在图片之前,它会被定为高优先级(High)。

  • 使用<script src="name.js"></script>加载的script,如果该标签出现在图片之后,它会被定为中优先级(Medium)。

  • 使用了asyncdefer属性的script,它会被定为低优先级(Low)。

  • 使用了type="module"属性的script,它会被定为低优先级(Low)。

Fonts 更像是难以驾驭的野兽;首先,它们是非常重要的资源(谁也不愿意发生这种情况:“我看到它了!”,“现在它不见了”,“哇,一个新字体!”)所以字体理应以最高优先级(Highest)下载。

不幸的是,大部分@font-face标签都包含在外链的css中(像这样加载:<link rel="stylesheet" href="file.css">)。这意味着字体往往会在样式加载完之后才进行加载。

即使你的CSS文件通过 @font-face引用了字体,也只在你的字体选择器匹配到页面上的节点时,才会请求该字体。假设你构建了一个单页面应用,在应用渲染之前并不会渲染任何文本,那么字体的加载时间会更加后置。

什么是关键请求?

大多数网站会让浏览器加载渲染页面所需的全部资源,并没有“可视区域/above the fold”的具体概念。

过去浏览器在同一域名下的同时请求数不会超过6个——人们通过使用assets-1.domain.tld, assets-2.domain.tld这种散列域名来尝试绕过这种限制,以便同时下载更多的资源,但是并未认识到这会导致每个新域名和资源都会需要DNS解析和TCP连接。

虽然这种方法有一些优点,但我们中的许多人并没有完全了解它的影响,当然也没有高质量的浏览器开发工具来验证它们。

谢天谢地,现在我们有了很棒的工具。以CNN为例,我们首先找到可视区域渲染完毕时(对于读者来说,就是可用时),必须加载的资源。

用户关注的内容是标题和主要内容。

展示该屏信息时,只有5个部分是必要的(而且在站点可用之前,并不需要把它们全部加载完):

  • 最重要的,HTML。假设其他请求都失败了,用户依旧可以阅读该页。

  • CSS。

  • Logo(一张用CSS放置的PNG背景图片。也可以是内嵌的SVG)。

  • 4 种web字体(居然这么多!)。

  • 文章头图

这些静态资源(注意不包括Javascript)是构成页面主视窗可视区域所必需的。它们应该首先被加载

但是我们通过Chrome的Performance面板看到,在字体和头图加载之前,大概有50个请求

在不太稳定的4G环境下记录,CNN.com大概在9s的时候才被完全渲染。

查看页面所需的请求,和实际的请求显然不一样。

控制资源加载优先级

现在我们已经定义了什么是关键请求,我们可以开始进行几个简单而强大的调整来对它们进行优先级排序。

预加载<link rel="preload" href="font.woff" />)让浏览器把font.woff放在加载队列的高优先级(High)中。

注意:font.woff应该以高优先级下载的原因是as="font"—它是一个字体,所以它遵循我们在之前“Chrome如何确立资源的优先级”一节所谈到的优先级规则。

其实这就好像你在告诉浏览器,“你可能还不知道它,但是我们将要使用它。”

这对我们之前定义的关键请求来说是完美的。Web字体几乎可以被定为"绝对关键",但是针对字体被发现并下载的方式有一些根本性问题:

  • 我们只有等到CSS被加载、解析和应用,才能发现@font-face规则。

  • 只有通过选择器将CSS规则匹配到DOM,才会将该字体添加到浏览器的请求队列中。

  • 选择器匹配发生在样式重新计算的过程中。它不一定在样式下载之后“立刻”发生,当主线程繁忙的时候,它可能被延迟。

在大多数情况下,字体的显示被延迟了好几秒钟,仅仅因为我们没有指示浏览器及时下载它们。

在一个拥有低端CPU,网络连接很慢的手机上,假设没有适当的回退方案,绝对会让用户抓狂。

预加载实践:字体

我对calibreapp.com跑了两个测试。在 第一个测试中,我并未对网站进行任何修改。在 第二个里面,我加了这两个标签:

<link rel="preload" as="font" href="" type="font/woff2" crossorigin />

<link rel="preload" as="font" href="" type="font/woff2" crossorigin />

下面,你将看到这两个测试中网页渲染的视觉对比。结果是相当惊人的:

当字体被预加载时,页面渲染速度加快了3.5秒

下面那行:字体是预加载的 — 这个网站在3G连接下5秒就渲染完成了。

<link rel="preload">还接受media=""属性,它将根据@media查询规则选择性地对资源进行优先排序:

<link rel="preload" href="article-lead-sm.jpg" as="image" type="image/jpeg" media="only screen and (max-width: 48rem)">

在这里,我们可以为小屏幕设备预加载特定的图像。对于“超棒头条图片”来说非常完美。

正如上面展示的,我们使用简单的几个标签,极大地改善了页面的请求和渲染性能。

在web字体上更进一步

69%网站使用了web字体, 不幸的是,他们在大多数情况下体验并不太好。这些文字出现,然后消失,然后再次出现,改变字重并在渲染过程中使页面发生抖动。

坦率地说,这几乎在每一个层面上都很糟糕。

正如您在上面看到的,控制字体的请求顺序和优先级对渲染速度有很大的影响。显然,我们应该在大多数情况下为Web字体请求排优先级

我们可以使用CSSfont-display属性进一步改进。这个属性允许我们在请求和加载Web字体过程中控制字体显示的方式。

显示方式有四个选项,但是我建议使用font-display: swap;,在Web字体加载完之前,使用后备字体进行显示--加载完成后直接替换原先的字体。

像这样定义一系列字体:

body {
  font-family: Calibre, Helvetica, Arial;
}

浏览器会在Calibre字体加载完成之前,先使用Helvetica字体(或者Arial字体,如果你没有先使用Helvetica字体字体的话)进行展示。现在只有Chrome和Opera支持font-display,但这是跨越性的异步,从今天起就没有理由不使用它了。

保持页面性能

正如你所知道的,网站从来都不是“完整的”。总是有改进的空间,这些技术方案会很快就让人感觉到应接不暇。

Calibre是一个自动化的工具,用于审核性能、可访问性和Web最佳实践,它将帮助你保持领先。

正如您在上面看到的,有几个指标是理解用户性能的关键。

  • 首次渲染,告诉我们什么时候浏览器开始“从无内容到有内容”

  • 首次有意义的渲染,告诉我们什么时候浏览器“渲染有用的内容”。

  • 最后,首次交互 将告诉你什么时候页面被完全渲染,并且JavaScript主线程已经落定(CPU已经平稳了几秒钟)。

在这里,我们为CNN的“首次有意义的渲染”设置了一个预期。

您可以针对所有这些关键用户体验指标设置预期。当超出预期(或满足预期)时,你的团队将通过Slack、电子邮件或你喜欢的任何方式得到通知。

Calibre 显示网络请求的优先级,这样你就可以确定所做的请求。调整优先级并改进性能。

我希望你已经学到了一些宝贵的技能来检查请求和它们的优先级,并已经想出一些方法来进行实验,以进行重大的性能改进。

关键请求的检查清单

  • ✅ 启用Chrome DevTools请求优先列表

  • ✅ 决定哪个请求必须在用户可以看到整个渲染页面前完成。

  • ✅ 尽可能减少所需的关键请求的数量。

  • ✅ 使用静态资源,这些资源“很可能”被使用在网站的下一页。

  • ✅ 使用<link https://example.com/other/styles.css rel=preload as=style>nopush HTTP请求头来告诉浏览器我们需要在HTML完全下载完之前预请求哪些资源。

  • 🚫 HTTP/2 Server push目前比较棘手。尽量避免使用它。 (看看Tom Bergan, Simon Pelchat 和 Michael Buettner写的informative document还有Jake Archibald的 "HTTP/2 Push is tougher than I thought"

  • ✅ 在可能的情况下对Web字体使用font-display: swap;

  • ⏱ Web字体有没有被使用?它们可以被移除吗?如果不能:提高优先它们的优先级并且使用WOFF2!

  • ⏱ 延迟加载脚本会延迟你的单页应用程序显示任何东西吗?

  • 📹 看看这个free screencast 还有 Front End Center ,它们告诉我们如何在加载字体时提供最好的降级体验。

  • 🔍 看下 chrome://net-internals/#events 然后加载一个页面—它会显示网络相关的事件。

  • 没有什么请求能比不请求更快[No request is faster than no request]. ✌️