Skip to content

图片懒加载

lazy-load是我们常见的优化方式,减少我们首屏图片请求个数,节省网络带宽。下面有几种实现方式 其实现原理如下图

懒加载原理

基础实现

js
// 获取所有的图片标签
const imgs = document.getElementsByTagName("img");
// 获取可视区域的高度
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
// num用于统计当前显示到了哪一张图片,避免每次都从第一张图片开始检查是否露出
let num = 0;
function lazyload() {
  for (let i = num; i < imgs.length; i++) {
    // 用可视区域高度减去元素顶部距离可视区域顶部的高度
    let distance = viewHeight - imgs[i].getBoundingClientRect().top;
    // 如果可视区域高度大于等于元素顶部距离可视区域顶部的高度,说明元素露出
    if (distance >= 0) {
      // 给元素写入真实的src,展示图片
      imgs[i].src = imgs[i].getAttribute("data-src");
      // 前i张图片已经加载完毕,下次从第i+1张开始检查是否露出
      num = i + 1;
    }
  }
}
// 监听Scroll事件
window.addEventListener("scroll", lazyload, false);
// 获取所有的图片标签
const imgs = document.getElementsByTagName("img");
// 获取可视区域的高度
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
// num用于统计当前显示到了哪一张图片,避免每次都从第一张图片开始检查是否露出
let num = 0;
function lazyload() {
  for (let i = num; i < imgs.length; i++) {
    // 用可视区域高度减去元素顶部距离可视区域顶部的高度
    let distance = viewHeight - imgs[i].getBoundingClientRect().top;
    // 如果可视区域高度大于等于元素顶部距离可视区域顶部的高度,说明元素露出
    if (distance >= 0) {
      // 给元素写入真实的src,展示图片
      imgs[i].src = imgs[i].getAttribute("data-src");
      // 前i张图片已经加载完毕,下次从第i+1张开始检查是否露出
      num = i + 1;
    }
  }
}
// 监听Scroll事件
window.addEventListener("scroll", lazyload, false);

IntersectionObserver 实现

IntersectionObserver MDN 文档

js
const imgs = document.querySelectorAll("img[data-src]");
const config = {
  rootMargin: "0px",
  threshold: 0,
};
let observer = new IntersectionObserver((entries, self) => {
  entries.forEach((entry) => {
    // 如果元素出现在视口内
    if (entry.isIntersecting) {
      let img = entry.target;
      let src = img.dataset.src;
      // 目标元素存在data-src
      if (src) {
        // 替换src
        img.src = src;
        img.removeAttribute("data-src");
      }
      // 解除观察
      self.unobserve(entry.target);
    }
  });
}, config);

imgs.forEach((image) => {
  observer.observe(image);
});
const imgs = document.querySelectorAll("img[data-src]");
const config = {
  rootMargin: "0px",
  threshold: 0,
};
let observer = new IntersectionObserver((entries, self) => {
  entries.forEach((entry) => {
    // 如果元素出现在视口内
    if (entry.isIntersecting) {
      let img = entry.target;
      let src = img.dataset.src;
      // 目标元素存在data-src
      if (src) {
        // 替换src
        img.src = src;
        img.removeAttribute("data-src");
      }
      // 解除观察
      self.unobserve(entry.target);
    }
  });
}, config);

imgs.forEach((image) => {
  observer.observe(image);
});

v-lazy 指令

js
export const lazyLoad = {
  bind: function (el, binding) {
    if (IntersectionObserver) {
      // 目标图片
      let targetSrc = binding.value; // 记录img原本要加载的图片地址

      // 占位图
      el.src = "xxx";

      let lazyImageObserver = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
          // 当前元素在视窗内可见
          if (entry.intersectionRatio > 0) {
            // 替换当前元素的src为目标图片地址
            el.src = targetSrc;

            // 使IntersectionObserver停止监听特定目标元素。释放资源
            lazyImageObserver.unobserve(el);
          }
        });
      });
      // 使IntersectionObserver开始监听一个目标元素。
      lazyImageObserver.observe(el);
    }
  },
};
export const lazyLoad = {
  bind: function (el, binding) {
    if (IntersectionObserver) {
      // 目标图片
      let targetSrc = binding.value; // 记录img原本要加载的图片地址

      // 占位图
      el.src = "xxx";

      let lazyImageObserver = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
          // 当前元素在视窗内可见
          if (entry.intersectionRatio > 0) {
            // 替换当前元素的src为目标图片地址
            el.src = targetSrc;

            // 使IntersectionObserver停止监听特定目标元素。释放资源
            lazyImageObserver.unobserve(el);
          }
        });
      });
      // 使IntersectionObserver开始监听一个目标元素。
      lazyImageObserver.observe(el);
    }
  },
};
html
<img v-lazy="目标图片" />
<img v-lazy="目标图片" />