延迟加载最佳实践

虽然延迟加载图片和视频可以带来可衡量的积极性能优势,但这项任务并不轻松。如果出错,可能会产生意想不到的后果。因此,请务必牢记以下几点。

注意折叠

使用 JavaScript 延迟加载网页上的所有媒体资源或许很有诱惑力,但您需要抵挡住这种诱惑。首屏上的所有内容都不应进行延迟加载。此类资源应被视为关键资源,因此应正常加载。

延迟加载会延迟加载资源,直到 DOM 可交互后,脚本加载完毕并开始执行为止。对于非首屏图片,没有问题,但首屏的关键资源应使用标准 <img> 元素加载,以便尽快显示。

当然,如今用户会在许多不同尺寸的屏幕上查看网站,因此首屏线的位置并不明确。笔记本电脑上位于首屏的内容在移动设备上很可能位于首屏。目前并没有放之四海而皆准的建议。您需要清点网页的关键资源,并以常规方式加载这些图片。

此外,您可能也不希望对折叠线那么严格,只将其用作触发延迟加载的阈值。对您来说,更理想的做法是在非首屏位置建立一个缓冲区,以便在用户将图片滚动到视口之前就能开始加载图片。例如,Intersection Observer API 允许您在创建新的 IntersectionObserver 实例时在 options 对象中指定 rootMargin 属性。这样可以有效地为元素提供缓冲区,以便在元素进入视口之前触发延迟加载行为:

let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
  // lazy-loading image code goes here
}, {
  rootMargin: "0px 0px 256px 0px"
});

如果 rootMargin 的值与您为 CSS margin 属性指定的值相似,那就是!在这种情况下,被观察元素的下外边距(默认为浏览器视口,但可以使用 root 属性更改为特定元素)扩大了 256 像素。这意味着,当图片元素距离视口不超过 256 像素时,系统将执行回调函数,并且图片将会在用户实际看到它之前开始加载。

如需在不支持 Intersection Observe 的浏览器中实现同样的效果,请使用滚动事件处理代码并调整 getBoundingClientRect 检查以包含缓冲区。

布局偏移和占位符

如果未使用占位符,延迟加载媒体可能会导致布局偏移。这些更改可能会使用户感到迷惑,并触发开销高昂的 DOM 布局操作,从而消耗系统资源并导致卡顿。至少,请考虑使用与目标图片占据相同尺寸的纯色占位符,或使用 LQIPSQIP 等技术,以在媒体项加载前对其内容进行提示。

对于 <img> 标记,src 最初应指向一个占位符,直到该属性使用最终图片网址更新为止。在 <video> 元素中使用 poster 属性可指向占位符图片。此外,请同时在 <img><video> 标记上使用 widthheight 属性。这样可以确保从占位符转换为最终图片时,不会在媒体加载时更改元素的渲染大小。

图像解码延迟

在 JavaScript 中加载大型图片并将其放入 DOM 可能会占用主线程,从而导致在解码时界面短时间无响应。在将图片插入 DOM 之前,使用 decode 方法异步解码图片可以减少此类卡顿,但请注意:此功能尚未在所有地方提供,而且会让延迟加载逻辑变得更加复杂。如果您想使用它,则需要进行检查。下面展示了如何结合使用 Image.decode() 和回退:

var newImage = new Image();
newImage.src = "my-awesome-image.jpg";

if ("decode" in newImage) {
  // Fancy decoding logic
  newImage.decode().then(function() {
    imageContainer.appendChild(newImage);
  });
} else {
  // Regular image load
  imageContainer.appendChild(newImage);
}

请访问此 CodePen 链接,查看与此示例类似的代码的实际运用。如果您的大多数图片都非常小,这对您而言可能没多大帮助,但肯定有助于减少延迟加载大型图片并将其插入 DOM 时出现的卡顿。

内容无法加载时

有时,媒体资源由于某种原因而加载失败,进而导致出现错误。何时会出现这种情况?具体视情况而定,但下面向您说明一种假设场景:您有短期(例如 5 分钟)的 HTML 缓存政策,用户访问相应网站,或者用户长时间(例如几小时)打开了一个过时的标签页,然后返回阅读您的内容。在此过程中的某个时刻,会发生重新部署。在此部署期间,映像资源的名称会因基于哈希的版本控制而发生更改,或者完全移除。当用户延迟加载图片时,相应资源已不可用,因此加载失败。

虽然这种情况相对罕见,但您可能有必要制定备用计划,以防延迟加载失败。对于图片,此类解决方案可能如下所示:

var newImage = new Image();
newImage.src = "my-awesome-image.jpg";

newImage.onerror = function(){
  // Decide what to do on error
};
newImage.onload = function(){
  // Load the image
};

发生错误时您决定执行的操作取决于您的应用。例如,您可以将图片占位符区域替换为一个按钮,使用户能够尝试再次加载该图片,或者只是在图片占位符区域显示错误消息。

此外,还可能会出现其他情况。无论您如何操作,在出现错误时向用户发出信号,并可能在出现问题时为用户提供操作提示永远不是坏主意。

JavaScript 可用性

不应假定 JavaScript 始终可用。如果您要延迟加载图片,不妨考虑提供 <noscript> 标记,以便在 JavaScript 不可用时显示图片。最简单的回退示例就是:在 JavaScript 处于关闭状态时,使用 <noscript> 元素传送图片:

我是图片!

如果 JavaScript 处于关闭状态,用户会同时看到占位符图片和 <noscript> 元素中包含的图片。为了解决此问题,请在 <html> 标记上添加 no-js 类,如下所示:

<html class="no-js">

然后,在通过 <link> 标记请求任何样式表之前,在 <head> 中放置一行内嵌脚本,该标记会在 JavaScript 处于开启状态时从 <html> 元素中移除 no-js 类:

<script>document.documentElement.classList.remove("no-js");</script>

最后,在 JavaScript 不可用时,使用一些 CSS 隐藏类为 lazy 的元素:

.no-js .lazy {
  display: none;
}

这不会阻止占位符图片加载,但结果会更令人满意。关闭 JavaScript 的用户不仅能得到占位符图片,还能看到比占位符和根本没有意义的图片内容更好的内容。