延迟加载图片

由于图片作为 <img> 元素或 CSS 背景图片内嵌在 HTML 中,因此可能会在网页上显示。在这篇博文中,您将了解如何延迟加载这两种类型的图片。

内嵌图片

最常见的延迟加载候选组件是 <img> 元素中使用的图片。对于内嵌图片,我们有三个延迟加载选项,这些选项可以结合使用,以便在各种浏览器中实现最佳兼容性:

使用浏览器级延迟加载

Chrome 和 Firefox 都支持通过 loading 属性进行延迟加载。此属性可以添加到 <img> 元素中,也可以添加到 <iframe> 元素中。lazy 值会指示浏览器立即加载图片(如果图片位于视口中),并在用户在图片附近滚动时提取其他图片。

如需详细了解浏览器支持,请参阅 MDN 浏览器兼容性表中的 loading 字段。如果浏览器不支持延迟加载,系统会忽略该属性,并照常立即加载图片。

对于大多数网站,向内嵌图片添加此属性可以提高性能,并节省加载他们可能永远都不会滚动浏览的图片的用户。如果您有大量图片,并且想要确保不支持延迟加载的浏览器用户能够带来延迟,您需要将此方法与下面介绍的方法之一结合使用。

如需了解详情,请参阅适用于网页的浏览器级延迟加载

使用 Intersection Observer

为了对 <img> 元素进行 polyfill 延迟加载,我们使用 JavaScript 检查这些元素是否在视口中。如果正确无误,系统会使用指向所需图片内容的网址填充其 src(有时是 srcset)属性。

如果您之前编写过延迟加载代码,那么您可能已使用 scrollresize 等事件处理脚本完成了任务。虽然这种方法在各个浏览器之间具有最强的兼容性,但现代浏览器提供了一种性能更佳且更高效的方法来通过 Intersection Observer API 检查元素可见性。

与依赖于各种事件处理脚本的代码相比,Intersection Observer 更易于使用和读取,因为您只需注册一个观察器即可监控元素,而无需编写冗长的元素可见性检测代码。您要做的就是确定元素可见时要执行的操作。我们为您的延迟加载的 <img> 元素假设以下基本标记模式:

<img class="lazy" src="placeholder-image.jpg" data-src="image-to-lazy-load-1x.jpg" data-srcset="image-to-lazy-load-2x.jpg 2x, image-to-lazy-load-1x.jpg 1x" alt="I'm an image!">

您应重点关注此标记的三个相关部分:

  1. class 属性,您在 JavaScript 中用于选择元素。
  2. src 属性,它引用页面首次加载时显示的占位图片。
  3. data-srcdata-srcset 属性,它们是占位符属性,包含元素进入视口后要加载的图片的网址。

现在,我们来看看如何在 JavaScript 中使用 Intersection Observer,以便通过以下标记模式延迟加载图片:

document.addEventListener("DOMContentLoaded", function() {
  var lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));

  if ("IntersectionObserver" in window) {
    let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          let lazyImage = entry.target;
          lazyImage.src = lazyImage.dataset.src;
          lazyImage.srcset = lazyImage.dataset.srcset;
          lazyImage.classList.remove("lazy");
          lazyImageObserver.unobserve(lazyImage);
        }
      });
    });

    lazyImages.forEach(function(lazyImage) {
      lazyImageObserver.observe(lazyImage);
    });
  } else {
    // Possibly fall back to event handlers here
  }
});

在文档的 DOMContentLoaded 事件中,此脚本会在 DOM 中查询类为 lazy 的所有 <img> 元素。如果 Intersection Observer 可用,请创建一个新的观察器,以便在 img.lazy 元素进入视口时运行回调。

Intersection Observer 适用于所有现代浏览器。因此,将其用作 loading="lazy" 的 polyfill 可确保大多数访问者都能使用延迟加载。

CSS 中的图片

虽然 <img> 标记是使用网页上的图片最常用的方式,但您也可以通过 CSS background-image 属性(和其他属性)调用图片。浏览器级延迟加载不适用于 CSS 背景图片,因此如果您有要延迟加载的背景图片,则需要考虑采用其他方法。

与加载时不考虑可见性的 <img> 元素不同,CSS 中的图片加载行为是通过更多的推测来进行的。构建文档和 CSS 对象模型以及渲染树后,浏览器会在请求外部资源之前检查 CSS 是如何应用于文档的。如果浏览器确定涉及外部资源的 CSS 规则不适用于当前构造的文档,则浏览器不会请求该文档。

这种推测行为可用于延迟 CSS 中图片的加载,具体方法是:使用 JavaScript 确定某个元素何时在视口内,然后将一个类应用于该元素,以应用调用背景图片的样式。这样,映像就会在需要时(而不是在初始加载时)下载。例如,假设一个元素包含一张大型主打背景图片:

<div class="lazy-background">
  <h1>Here's a hero heading to get your attention!</h1>
  <p>Here's hero copy to convince you to buy a thing!</p>
  <a href="/buy-a-thing">Buy a thing!</a>
</div>

div.lazy-background 元素通常会包含由某些 CSS 调用的主打背景图片。不过,在下面的延迟加载示例中,您可以通过在元素位于视口中时向其添加 visible 类来隔离 div.lazy-background 元素的 background-image 属性:

.lazy-background {
  background-image: url("hero-placeholder.jpg"); /* Placeholder image */
}

.lazy-background.visible {
  background-image: url("hero.jpg"); /* The final image */
}

从这里开始,使用 JavaScript 检查该元素是否在视口中(使用 Intersection Observer!),并在此时将 visible 类添加到 div.lazy-background 元素中,以加载图片:

document.addEventListener("DOMContentLoaded", function() {
  var lazyBackgrounds = [].slice.call(document.querySelectorAll(".lazy-background"));

  if ("IntersectionObserver" in window) {
    let lazyBackgroundObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          entry.target.classList.add("visible");
          lazyBackgroundObserver.unobserve(entry.target);
        }
      });
    });

    lazyBackgrounds.forEach(function(lazyBackground) {
      lazyBackgroundObserver.observe(lazyBackground);
    });
  }
});

对 Largest Contentful Paint (LCP) 的影响

延迟加载是一项出色的优化功能,可将图片延迟到实际需要时才加载,从而减少启动期间的总体流量消耗和网络争用情况。这可以缩短启动时间,并通过减少图像解码所需的时间来减少主线程上的处理。

不过,如果您太过急于使用延迟加载,这种技术可能会对您网站的 Largest Contentful Paint LCP 产生负面影响。您需要避免延迟加载启动期间视口中的图片。

使用基于 JavaScript 的延迟加载器时,您需要避免延迟加载视口内图片,因为这些解决方案通常会使用 data-srcdata-srcset 属性作为 srcsrcset 属性的占位符。问题在于,这些图片的加载将会延迟,因为浏览器的预加载扫描程序在启动过程中找不到这些图片

即使使用浏览器级延迟加载来延迟加载视口内图片,也可能会适得其反。将 loading="lazy" 应用于视口内图片后,该图片将被延迟,直到浏览器确定它位于视口中为止,这可能会影响网页的 LCP。

切勿在启动期间延迟加载视口中可见的图片。这种模式会对网站的 LCP 产生负面影响,进而对用户体验产生负面影响。如果您需要在启动时加载图片,请在启动时尽快加载,而不要延迟加载!

延迟加载库

您应尽可能使用浏览器级延迟加载,但如果您发现自己无法选择该方法(例如有大量用户仍然使用旧版浏览器),则可以使用以下库来延迟加载图片:

  • lazysizes 是功能全面的延迟加载库,可延迟加载图片和 iframe。它使用的模式与此处显示的代码示例非常相似,因为它会自动绑定到 <img> 元素的 lazyload 类,并要求您在 data-src 和/或 data-srcset 属性中指定图片网址,而这些网址的内容会分别交换为 src 和/或 srcset 属性。它使用 Intersection Observer(您可以使用 polyfill),并且可通过多种插件进行扩展,以便执行延迟加载视频等操作。详细了解如何使用 lazysizes
  • vanilla-lazyload 是一个轻量级选项,用于延迟加载图片、背景图片、视频、iframe 和脚本。它利用了 Intersection Observer,支持自适应图片,并且支持浏览器级别的延迟加载。
  • lozad.js 是另一个仅使用 Intersection Observer 的轻量级选项。因此,它的性能非常出色,但需要先进行 polyfill,然后才能在旧版浏览器上使用。
  • 如果您需要 React 专用的延迟加载库,请考虑使用 react-lazyload。虽然它不使用 Intersection Observer,但确实为习惯于使用 React 开发应用的开发者提供了熟悉的图像延迟加载方法。