延迟加载最佳实践

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

注意首屏

使用 JavaScript 对网页上的所有媒体资源进行延迟加载可能会很有诱惑力,但您必须抵挡住这种诱惑。首屏的任何内容均不应进行延迟加载。此类资源应被视为关键资源,因此应正常加载。

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

当然,如今用户通过如此多不同尺寸的屏幕查看网站,首屏线的位置并不是那么明确。在移动设备上,位于首屏的内容可能位于首屏下方。目前并没有完全可靠的建议,无法在每种情况下完美解决这个问题。您需要清点网页的关键资源,并以典型方式加载这些图片。

此外,您可能也不希望将折线作为触发延迟加载的阈值如此严格。对您来说,更理想的做法是在首屏线以下一定距离内建立缓冲区,以便在用户将图片滚动到视口之前就开始加载图片。例如,Intersection Observer API 允许您在创建新的 IntersectionObserver 实例时在选项对象中指定 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 不可用时显示图片。最简单的回退示例包括使用 <noscript> 元素在 JavaScript 处于关闭状态时传送图片:

我是图片!

如果 JavaScript 已关闭,用户将同时看到占位符图片和 <noscript> 元素中包含的图片。为了解决此问题,请在 <html> 标记中放置一个 no-js 类,如下所示:

<html class="no-js">

然后,在通过 <link> 标记请求任何样式表之前,在 <head> 中放置一行内嵌脚本,这些标记会从 <html> 元素中移除 no-js 类(如果 JavaScript 已开启):

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

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

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

这不会阻止占位符图片加载,但结果却更理想。关闭 JavaScript 的用户不只是看到占位符图片,这比使用占位符和没有任何有意义的图片内容更好。