图片懒加载不完全指南

图片懒加载不完全指南

 
1 人赞同了该文章

图片懒加载

在日常开发中,我们常用的两种图片加载方式如下:

  • 使用 img 标签加载图片;
  • 使用 css background 加载图片。

在这篇文章中,您将了解如何延迟加载这两种类型的图像。

img 标签图片懒加载

对于 img 标签,我们有三个延迟加载方案,同时也可以组合使用以实现跨浏览器的最佳兼容性:

  • 使用浏览器提供的 API:loading="lazy"
  • 使用 Intersection Observer
  • 使用 scroll 和 resize 事件进行处理

使用浏览器级别的懒加载

Chrome 和 Firefox 都支持使用 loading 属性的延迟加载。把此属性添加到 <img> 元素并设置其值为 lazy 相当于告诉浏览器,如果图像在可视区域中,就立即加载它,反之则不加载。

至于加载的视口距离阈值,目前是没办法修改的,chrome 会根据当前的网络情况设置对应的阈值。

对于2020 年 7 月以后的 chrome版本,4G 网络下的视口距离阈值是 1250 像素,3G 网络下是 2500 像素。

需要注意的是我们也需要手动指定图片的宽高,否则我们可能会看到布局发生偏移。一个完整的代码实例如下:

<img src="image.png" loading="lazy" alt="…" width="200" height="200">

demo 如下:

loading=lazy demo

有关浏览器支持的详细信息,可以参阅MDN的浏览器兼容性表。如果浏览器不支持延迟加载,那么该属性将被忽略,图像将像往常一样立即加载。

个人觉得它的缺点或者不够好的地方有以下几点:

  1. 不能自定义加载的视口距离阈值;
  2. 官方建议首屏内的图片不设置 loading="lazy" , 这需要一些额外的使用成本;
  3. 无法设置加载中情况下的占位图;
  4. 暂时没有对应的 polyfil,需要我们自己结合下面介绍的两种方式之一组合使用。

有关 loading 属性更详细的内容请阅读这篇文章

使用 Intersection Observer

我们需要使用 JavaScript 来判断图片是否在视图中,如果是,则将真实的 url 地址填充到图片的 src 中。如果之前了解过延迟加载,可能我们想到的是通过 scroll 事件来判断图片是否达到可视条件。虽然这种方法是兼容性最好的,但现代浏览器提供了一种性能更高、效率更高的方法:通过 Intersection Observer API 来完成检查元素可见性的工作。

相比于依赖各种事件处理程序的实现方式,Intersection Observer 更易于使用和阅读,因为您只需注册一个观察者即可观察元素,而无需编写繁琐的元素可见性检测代码。剩下要做的就是决定当元素可见时要做什么。

它的语法如下:

const observer = new IntersectionObserver(callback, options)
// options 支持以下选项:
// {
//  root: null,  // 所监听对象的具体根元素。如果未传入值或值为null,则默认使用顶级文档的视窗。
//  rootMargin: "30px 100px 50px", // 可以简单理解为增大根元素的范围,用来修改可见的触发范围
// thresholds: [0, 0.5, 1], // 用来指定交叉比例,决定什么时候触发回调函数,是一个数组,默认是[0]。
   // 上面代码,我们指定了交叉比例为0,0.5,1,当观察元素img0%、50%、100%时候就会触发回调函数
// }

假设我们有如下图片元素:

<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 属性,我们用来获取所有需要懒加载的图片元素;
  2. src 属性,引用占位图片;
  3. data-src 和 data-srcset 属性,它们是占位符属性,包含元素在进入可视区后将加载的图像的 URL。

接下里我们看如何使用Intersection Observer 来实现懒加载:

// 1. 在DOMContentLoaded事件触发时,获取所有需要懒加载的 img 标签。
document.addEventListener("DOMContentLoaded", function() {
  var lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));

  // 2. 如果IntersectionObserver可用,创建一个新的观察者,
  // 当 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 {
    // 不支持IntersectionObserver环境下的 fallback
  }
});

使用事件进行处理

如果浏览器不支持上面所说的Intersection Observer,最简答的办法就是使用它的 polyfill,或者回退到使用 scrollresize和可能用到的orientationchange事件配合getBoundingClientRect方法来确定元素是否在可视区域中。

实现的思路如下:假设与之前的 img 标签示例相同,我们还是使用 lazy 这个 class 来获取要懒加载的图片元素,使用 setTimeout 和 active 标记 来做节流,当一个元素加载之后,将其从列表中移除。当整个需要懒加载的列表都被清空后,移除对事件的监听。示例代码如下:

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

  const lazyLoad = function() {
    if (active === false) {
      active = true;

      setTimeout(function() {
        lazyImages.forEach(function(lazyImage) {
          if ((lazyImage.getBoundingClientRect().top <= window.innerHeight && lazyImage.getBoundingClientRect().bottom >= 0) && getComputedStyle(lazyImage).display !== "none") {
            lazyImage.src = lazyImage.dataset.src;
            lazyImage.srcset = lazyImage.dataset.srcset;
            lazyImage.classList.remove("lazy");

            lazyImages = lazyImages.filter(function(image) {
              return image !== lazyImage;
            });

            if (lazyImages.length === 0) {
              document.removeEventListener("scroll", lazyLoad);
              window.removeEventListener("resize", lazyLoad);
              window.removeEventListener("orientationchange", lazyLoad);
            }
          }
        });

        active = false;
      }, 200);
    }
  };

  document.addEventListener("scroll", lazyLoad);
  window.addEventListener("resize", lazyLoad);
  window.addEventListener("orientationchange", lazyLoad);
});

css background 图片懒加载

和 img 标签实现方式类似,我们可以使用 JS 来判断元素是否可见,然后更改元素的 class 来实现。

举个简单的例子:

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

 

// lazy-background.js
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);
    });
  }
});

 

参考文章

The Complete Guide to Lazy Loading Images

posted on 2024-04-14 14:48  漫思  阅读(15)  评论(0编辑  收藏  举报

导航