延迟加载图片

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

内嵌图片

<img> 元素中使用的图片是最常见的延迟加载对象。使用内嵌图片时,我们有三种延迟加载选项,它们可组合使用以实现各种浏览器的最佳兼容性:

使用浏览器级延迟加载

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

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

对于大多数网站,向内嵌图片添加此属性可以提升性能,并减少用户加载他们可能永远不会滚动到的图片。如果您有大量图片,并且希望确保浏览器的用户不支持延迟加载,您需要将此方式与下面介绍的其中一种方法结合使用。

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

使用 Intersection Observer

为了对 <img> 元素执行延迟加载,我们使用 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 调用的主打背景图片。不过,在此延迟加载示例中,您可以通过向 div.lazy-background 元素添加位于视口中的 visible 类来隔离该元素的 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 开发应用的用户提供熟悉的图片延迟加载方法。