深入浅出图像和框架的原生延迟加载

Avatar of Erk Struwe
Erk Struwe

DigitalOcean 为您旅程的各个阶段提供云产品。 立即开始使用 $200 免费信用额度!

如今的网站充斥着图像和视频等大型媒体资源。 图像占平均网站流量的 约 50%。 然而,其中许多图像从未向用户显示,因为它们放置在远离 视窗区域 的位置。

您可能想知道图像延迟加载是怎么回事? 延迟加载 是我们在 CSS-Tricks 上 多次介绍 的内容,包括一份 使用 JavaScript 的不同方法的详细文档指南。 简而言之,我们正在谈论一种机制,该机制会延迟加载内容所需的网络流量,直到需要它为止 - 更准确地说,当内容进入视窗时才触发加载。

好处是什么? 页面初始尺寸更小,加载速度更快,并为可能不需要的项目节省了网络请求(如果用户从未到达该项目)。

如果您阅读了其他关于延迟加载的指南(在本网站或其他网站上),您会发现我们不得不采取不同的策略来使延迟加载工作。 好吧,这种情况即将改变,因为延迟加载将在 HTML 中以新的 loading 属性的形式提供原生支持… 至少在 Chrome 中,这有望带来更广泛的采用。 Chrome 目前正在开发和测试对原生延迟加载的支持,预计将在 Chrome 76 中启用,该版本计划于 2019 年 7 月 30 日发布。

Eager cat loaded lazily (but still immediately because it's above the fold)

原生方法之前的方案

到目前为止,像我们这样的开发人员不得不使用 JavaScript(无论是库还是从头开始编写的代码)才能实现延迟加载。 大多数库的工作方式如下

  • 初始的服务器端 HTML 响应包含一个没有 src 属性的 img 元素,因此浏览器不会加载任何数据。 相反,图像的 URL 被设置为元素数据集中另一个属性的值,例如 data-src
  • <img data-src="https://tiny.pictures/example1.jpg" alt="...">
  • 然后,加载并执行延迟加载库。
  • <script src="LazyLoadingLibrary.js"></script>
    <script>LazyLoadingLibrary.run()</script>
  • 它跟踪用户的滚动行为,并在图像即将滚动到视窗内时告诉浏览器加载该图像。 它通过将 data-src 属性的值复制到之前为空的 src 属性来实现这一点。
  • <img src="https://tiny.pictures/example1.jpg" data-src="https://tiny.pictures/example1.jpg" alt="...">

这种方法已经行之有效,并能完成任务。 但它并不理想,原因如下。

这种方法的明显问题是网站显示的 关键路径 的长度。 它包含三个步骤,这些步骤必须按顺序执行(一个接一个)

  1. 加载初始 HTML 响应
  2. 加载延迟加载库
  3. 加载图像文件

如果这种技术用于视窗区域上方的图像,网站在加载过程中会闪烁,因为它首先在没有图像的情况下绘制(在步骤 1 或 2 之后,具体取决于脚本是否使用 deferasync),然后在加载图像后包含该图像。 它也会让人感觉加载缓慢。

此外,延迟加载库本身会对网站的带宽和 CPU 要求造成 额外负担。 而且,别忘了 JavaScript 方法不适用于禁用 JavaScript 的用户(尽管我们不应该在 2019 年关心他们,应该吗?)。

哦,还有像 CSS-Tricks 这样依赖 RSS 分发内容的网站怎么办? 初始的无图像渲染意味着 RSS 版本的内容中也没有图像。

等等。

原生延迟加载来拯救!

Lazy cat loaded lazily

正如我们在开头提到的,Chromium 和 Google Chrome 将从 Chrome 75 开始以新的 loading 属性的形式提供原生延迟加载机制。 我们将稍后介绍该属性及其值,但让我们首先在浏览器中让它工作,以便我们一起查看。

启用原生延迟加载

在 Chrome 75 及更高版本中,我们可以通过切换两个标志手动启用延迟加载。 预计 Chrome 将从 76 版本开始默认启用此功能(计划于 2019 年 7 月 30 日发布)。

  1. 在 Chromium 或 Chrome Canary 中打开 chrome://flags
  2. 搜索 lazy
  3. 启用“启用延迟图像加载”和“启用延迟框架加载”标志。
  4. 使用屏幕右下角的按钮重新启动浏览器。
Google Chrome 中的原生延迟加载标志

您可以通过打开 JavaScript 控制台 (F12) 来检查此功能是否已正确启用。 您应该看到以下警告

“[干预] 延迟加载的图像被替换为占位符。 加载事件被延迟。”

都准备好了吗? 现在我们可以深入研究 loading 属性了。

loading 属性

imgiframe 元素都接受 loading 属性。 重要的是要注意,浏览器不会将它的值视为严格的顺序,而是将其视为提示,以帮助浏览器自行决定是否延迟加载图像或框架。

该属性可以具有三个值,如下所述。 在图像旁边,您会发现表格,其中列出了此页面加载的各个资源加载计时。 范围响应 指的是一种部分预先飞行请求,用于确定图像的尺寸(有关详细信息,请参见 工作原理)。 如果此列已填充,则浏览器已成功执行范围请求。

请注意 startTime 列,它表示 DOM 解析后延迟图像加载的时间。 您可能需要执行硬重载 (CTRL + Shift + R) 才能重新触发范围请求。

auto(或 unset)值

<img src="auto-cat.jpg" loading="auto" alt="...">
<img src="auto-cat.jpg" alt="...">
<iframe src="https://css-tricks.cn/" loading="auto"></iframe>
<iframe src="https://css-tricks.cn/"></iframe>
Auto cat loaded automatically
自动加载的猫

loading 属性设置为 auto(或直接省略值,例如 loading="")可以让 浏览器自行决定 是否延迟加载图像。 它会考虑很多因素来做出决定,例如平台、是否启用了数据节省模式、网络状况、图像大小、图像与框架、CSS display 属性等。(有关所有这些因素为何重要的信息,请参见 工作原理)。

eager 值

<img src="auto-cat.jpg" loading="eager" alt="...">
<iframe src="https://css-tricks.cn/" loading="eager"></iframe>
Eager cat loaded eagerly
立即加载的猫

eager 值为浏览器提供了提示,表明应 立即加载 图像。 如果加载已被延迟(例如,因为它已设置为 lazy,然后由 JavaScript 更改为 eager),浏览器应该立即开始加载图像。

lazy 值

<img src="auto-cat.jpg" loading="lazy" alt="...">
<iframe src="https://css-tricks.cn/" loading="lazy"></iframe>
Lazy cat loaded lazily
延迟加载的猫

lazy 值为浏览器提供了提示,表明应延迟加载图像。 浏览器会自行解释这到底意味着什么,但 解释文档 中指出,它应该在用户滚动到图像 “附近” 时开始加载,这样当图像实际进入视窗时,它可能已经加载完毕。

加载属性的工作原理

与 JavaScript 延迟加载库形成对比的是,原生延迟加载使用一种预先请求的方式来获取 **图像文件的首个 2048 字节**。使用这些字节,浏览器尝试确定图像的尺寸,以便为完整图像插入一个不可见的占位符,并防止内容在加载过程中跳动。

图像的 `load` 事件会在完整图像加载后立即触发,无论是第一次请求(对于小于 2 KB 的图像)还是第二次请求。请注意,对于某些图像,load 事件可能永远不会触发,因为不会进行第二次请求。

将来,浏览器可能比当前提议中的请求次数多一倍。首先是范围请求,然后是完整请求。确保您的服务器支持 HTTP `Range: 0-2047` 标头并以状态码 `206 (Partial Content)` 响应,以防止它们两次传送完整图像。

由于同一用户发出的后续请求数量增加,Web 服务器对 HTTP/2 协议的支持将变得更加重要。

让我们谈谈延迟内容。Chrome 的渲染引擎 Blink 使用启发式方法来确定哪些内容应该被延迟以及延迟多长时间。您可以在 Scott Little 的设计文档 中找到对要求的全面列表。以下是对将被延迟的内容的简短概述。

  • 所有平台上设置了 `loading="lazy"` 的图像和框架。
  • Chrome for Android 上启用了 Data Saver 且满足以下所有条件的图像。
    • loading="auto" 或未设置。
    • 没有 `width` 和 `height` 属性,小于 10 像素。
    • 不是在 JavaScript 中以编程方式创建的。
  • 满足以下所有条件的框架。
    • loading="auto" 或未设置。
    • 来自第三方(与嵌入页面的域名或协议不同)。
    • 高度和宽度大于 4 像素(以防止延迟微小的跟踪框架)。
    • 未标记为 `display: none` 或 `visibility: hidden`(同样,以防止延迟跟踪框架)。
    • 没有使用负 `x` 或 `y` 坐标定位在屏幕外。

具有 srcset 的响应式图像

原生延迟加载也适用于使用 `srcset` 属性的响应式 `img` 元素。该属性为浏览器提供了一个图像文件候选列表。根据用户的屏幕尺寸、显示像素比率、网络状况等,浏览器将选择 **最适合当前情况的图像候选**。像 tiny.pictures 这样的图像优化 CDN 能够 **无需任何后端开发** 就实时提供所有图像候选。

<img src="https://demo.tiny.pictures/native-lazy-loading/lazy-cat.jpg" srcset="https://demo.tiny.pictures/native-lazy-loading/lazy-cat.jpg?width=400 400w, https://demo.tiny.pictures/native-lazy-loading/lazy-cat.jpg?width=800 800w" loading="lazy" alt="...">

浏览器支持

在撰写本文时,还没有浏览器默认支持原生加载。但是,正如我们所述,Chrome 将从 Chrome 76 开始启用此功能。其他浏览器供应商尚未宣布支持。(Edge 是一种例外,因为它很快将 切换到 Chromium。)

您可以使用几行 JavaScript 代码检测此功能。

if ("loading" in HTMLImageElement.prototype) {
  // Support.
} else {
  // No support. You might want to dynamically import a lazy-loading library here (see below).
}

查看
Native lazy-loading browser support
by Erk Struwe (@erkstruwe)
on CodePen.

自动回退到使用低质量图像占位符的 JavaScript 解决方案

大多数基于 JavaScript 的延迟加载库的一个非常酷的功能是 低质量图像占位符 (LQIP)。它利用了这样一个想法,即浏览器立即加载(或者我应该说曾经加载)`img` 元素的 `src`,即使它后来被另一个 URL 替换。这样,就可以在页面加载时加载一个微小的文件大小、低质量的图像文件,然后用一个全尺寸版本替换它。

现在,我们可以使用它来模拟原生延迟加载的 2 KB 范围请求,在不支持此功能的浏览器中实现相同的结果,即具有实际图像尺寸和微小文件大小的占位符。

查看
Native lazy-loading with JavaScript library fallback and low-quality image placeholder
by Erk Struwe (@erkstruwe)
on CodePen.

结论

我真的很兴奋这个功能。坦率地说,我还想知道为什么它直到现在还没有得到更多关注,考虑到它的发布即将到来,即使启发式方法中只有一小部分发生变化,对全球互联网流量的影响也将是显著的。

想想看:在不同 Chrome 平台上逐步推出后,`auto` 成为默认设置,**世界上最流行的浏览器很快就会默认延迟加载屏幕外的图像和框架。** 许多编写不当的网站的流量数量不仅会大幅下降,而且 Web 服务器会受到用于图像尺寸检测的微小请求的轰炸。

然后是跟踪:假设许多不知情的跟踪像素和框架将无法加载,分析和联盟行业将不得不采取行动。我们只能希望他们不要惊慌失措,并为每个图像添加 `loading="eager"`,使这个伟大的功能对他们的用户毫无用处。他们应该改变他们的代码,以便在 上面 描述的启发式方法中被识别为跟踪像素。

Web 开发人员、分析和运营经理应该立即检查他们的网站在此功能下的行为以及他们的服务器对 `Range` 请求和 HTTP/2 的支持。

如果预计有任何问题,或者您希望将图像传送优化推向极致(包括自动 WebP 支持、低质量图像占位符等等),图像优化 CDN 可以提供帮助。阅读更多关于 tiny.pictures 的内容!

参考资料