Vue2 中自定义图片懒加载指令 2.0

Vue2 中自定义图片懒加载指令 2.0

由于我前天分享的Vue2 中自定义图片懒加载指令数据很好,所以我在这里继续分享第二种实现方法 — 使用Intersection Observer API

这种方法实现与使用起来都更简单,性能也更优,而唯一缺点就是兼容性没这么好,但也基本能兼容市面上的大部分浏览器(IE 除外),相信后面兼容性肯定会越来越好的,所以大家可以提前学起来哦。

对该 API 不熟悉的朋友可以去看看我的这篇大白话详解 Intersection Observer API博客。

没看过我前天分享的Vue2 中自定义图片懒加载指令博客的朋友,也强烈推荐去看看哦。

2.图片懒加载指令的基本介绍

2.1 最终的实现效果

最终效果如下图:
在这里插入图片描述

2.2 图片懒加载指令的注册与使用

由于在个人博客系统中图片懒加载指令使用的比较频繁,所以我依旧选择了全局注册该指令。

因为Intersection Observer API内部会与异步检测目标元素与根元素相交情况的变化。所以我们就不用再使用事件总线来进行通信了,所以也就不需引入事件总线配置文件。

main.js入口文件的代码如下:

import Vue from "vue";
import App from "./App.vue";

import vLazy from "./directives/lazy";
Vue.directive("lazy", vLazy); //全局注册指令

new Vue({
    
    
  render: (h) => h(App),
}).$mount("#app");

由于不用事件总线进行通信,使用起来及其方便,只需将 img 图片元素的 src 属性绑定到 v-lazy 自定义指令上即可。
所以v-lazy 指令的示例代码如下:

<template>
  <div class="container">
    <ul>
      <li v-for="img in imgs" :key="img.id">
        <img v-lazy="img.src" :alt="img.alt" :title="img.title" />
      </li>
    </ul>
  </div>
</template>
<script>
export default {
  data() {
    return {
      imgs: [
        {
          id: "",
          src: "",
          alt: "",
          title: "",
        },
      ],
    };
  },
};
</script>

2. 实现图片懒加载的原理

Intersection Observer API不熟悉的朋友可以去看看我的这篇大白话详解 Intersection Observer API博客。(写这篇博客花了很多心思,还画了许多图哦)

2.1 与上篇博客实现原理的对比

在上篇博客中,我提出以下四个问题,并进行解答,概括起来如下:

  1. 如何监听容器的滚动条的滚动?
    • 当时我们使用了事件总线方法来监听父容器元素的滚轮事件,并使用了事件防抖工具函数进行了性能优化。
  2. 使用自定义指令哪些钩子函数?
    • 当时我们是使用insertedunbind这两个钩子函数,并使用 imgs 数组收集 img 的 DOM 与 src 信息。
  3. 如何判断图片 img 元素是否在用户的可见范围内?
    • 当时我们是使用Element.clientWidthElement.getBoundingClientRect()这两个 API,并对 API 获得的信息进行了逻辑判断。
  4. 如何处理图片 img 元素的加载?
    • 当时我们是用 Image 对象实例代替 img 目标元素加载图片,并对Image 对象onload事件进行了改写,在其内部执行 img 目标元素的 src 属性替换操作。

而在上篇博客中,对于 问题 2 与 问题 4 的解决办法几乎没变,而 问题 1 与 问题 3 这=就可以直接使用Intersection Observer API轻松解决,具体如下:

  1. 如何监听容器的滚动条的滚动?
    • 我们只需将IntersectionObserver对像的根元素设为视口即可。(不传 root 属性与 threshold 属性)
  2. 使用自定义指令哪些钩子函数?
    • 当时我们是使用insertedunbind这两个钩子函数,并使用 imgsInf 数组收集 img 的 DOM 与 src 信息。(不变,只进行了变量名更改)
  3. 如何判断图片 img 元素是否在用户的可见范围内?
    • 我们只需使用IntersectionObserverEntry对象isIntersecting属性进行判断即可。
  4. 如何处理图片 img 元素的加载?
    • 当时我们是用 Image 对象实例代替 img 目标元素加载图片,并对Image 对象onload事件进行了改写,在其内部执行 img 目标元素的 src 属性替换操作。(不变,只是进行了函数封装)

两篇博客的详细对比见下表:

上篇博客的解决办法 这篇博客的解决办法
如何监听容器的滚动条的滚动? 事件总线 + 函数防抖 IntersectionObserver对像root属性设为视口
使用自定义指令哪些钩子函数? inserted + unbind 与上篇博客一致,只进行了变量名更改
如何判断图片 img 元素是否在用户的可见范围内? clientWidth + getBoundingClientRect() IntersectionObserverEntry对象isIntersecting属性
如何处理图片 img 元素的加载? 用 Image 对象实例代替加载 与上篇博客一致,只进行了函数封装

大家就这么看起来应该都知道Intersection Observer API的方法的实现更简单了。

2.2 实现步骤

  1. 我们先把解决问题 2 与问题 4 的代码直接写出来:
import defaultGif from "@/assets/default.gif"; //在assets静态文件夹下放入默认图

let imgsInf = []; //存储尚未加载的图片信息

//调用该函数 用于加载单张图片
function loadingImg(imgDOM) {
    
    
  //获得该img元素的src信息
  let imgSrc = imgsInf.filter((imgInf) => imgInf.dom === imgDOM)[0].src;
  const tempImg = new Image(); //新建Image对象实例
  //改写onload事件
  tempImg.onload = function () {
    
    
    // 当图片加载完成之后 替换img元素的src属性
    imgDOM.src = imgSrc;
  };
  tempImg.src = imgSrc; //Image实例代替加载
  imgsInf = imgsInf.filter((imgInf) => imgInf.dom !== imgDOM); //将已加载好的图片进行删除
}

export default {
    
    
  inserted(el, bindings) {
    
    
    //刚插入父节点时
    el.src = defaultGif; // 先暂时使用默认图片的src

    const imgInf = {
    
    
      dom: el, //img 元素DOM节点
      src: bindings.value, //img的真正src信息
    };
    imgsInf.push(imgInf); //存储img元素的src信息
  },
  unbind(el) {
    
    
    //解绑时 清空 imgs

    imgsInf = imgsInf.filter((imgInf) => imgInf.dom !== el);
  },
};
  1. 创建IntersectionObserver对象并进行相应的配置,代码如下:
//io 为 IntersectionObserver对象 - 由IntersectionObserver()构造器创建
const io = new IntersectionObserver((entries) => {
    
    
  //entries 为 IntersectionObserverEntry对像数组
  entries.forEach((item) => {
    
    
    //item为 IntersectionObserverEntry 对象

    // isIntersecting是一个Boolean值,判断目标元素当前是否可见
    if (item.isIntersecting) {
    
    
      //img元素可见时
      loadingImg(item.target); //加载该img元素
      io.unobserve(item.target); //图片加载完后即停止监听该元素
    }
  });
}); //不传options参数
//默认根元素(root属性)为浏览器视口、
//默认阈值(threshold属性)为 0,表示目标元素刚进入根元素可见范围时触发回调函数
  1. 在合适的位置使用IntersectionObserver对象observe开始监听方法与unobserve停止监听方法,所以整体代码如下:
import defaultGif from "@/assets/default.gif"; //在assets静态文件夹下放入默认图

let imgsInf = []; //存储尚未加载的图片信息

//调用该函数 用于加载单张图片
function loadingImg(imgDOM) {
    
    
  //获得该img元素的src信息
  let imgSrc = imgsInf.filter((imgInf) => imgInf.dom === imgDOM)[0].src;
  const tempImg = new Image(); //新建Image对象实例

  //改写onload事件
  tempImg.onload = function () {
    
    
    // 当图片加载完成之后 替换img元素的src属性
    imgDOM.src = imgSrc;
  };

  tempImg.src = imgSrc; //Image实例代替加载
  imgsInf = imgsInf.filter((imgInf) => imgInf.dom !== imgDOM); //将已加载好的图片进行删除
}

//io 为 IntersectionObserver对象 - 由IntersectionObserver()构造器创建
const io = new IntersectionObserver((entries) => {
    
    
  //entries 为 IntersectionObserverEntry对像数组
  entries.forEach((item) => {
    
    
    //item为 IntersectionObserverEntry 对象

    // isIntersecting是一个Boolean值,判断目标元素当前是否可见
    if (item.isIntersecting) {
    
    
      //img元素可见时
      loadingImg(item.target); //加载该img元素
      io.unobserve(item.target); //图片加载完后即停止监听该元素
    }
  });
}); //不传options参数
//默认根元素(root属性)为浏览器视口、
//默认阈值(threshold属性)为 0,表示目标元素刚进入根元素可见范围时触发回调函数

export default {
    
    
  inserted(el, bindings) {
    
    
    //刚插入父节点时
    el.src = defaultGif; // 先暂时使用默认图片的src
    io.observe(el); //开始监听该img元素
    const imgInf = {
    
    
      dom: el, //img 元素DOM节点
      src: bindings.value, //img的真正src信息
    };
    imgsInf.push(imgInf); //存储img元素的src信息
  },
  unbind(el) {
    
    
    //某个img元素解绑时 清除该img元素的信息 并停止监听该img元素
    io.unobserve(el); //停止监听
    imgsInf = imgsInf.filter((imgInf) => imgInf.dom !== el); //清除存储信息
  },
};

这方法的逻辑判断比上篇博客的方法简单的多,所以学会了一定会给我们开发带来便利。

3. Intersection Observer API 的兼容性

Intersection Observer API的兼容性如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l7VE7frt-1663962746438)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/562af71865f84020be86acefe260b263~tplv-k3u1fbpfcp-watermark.image?)]

大家在实际项目中选择性使用哦。

详情大家可参考CAN I USE - intersectionobserver

参考博客

结语

这是我目前所了解的知识面中最好的解答,当然也有可能存在一定的误区。

所以如果对本文存在疑惑,可以在评论区留言,我会及时回复的,欢迎大家指出文中的错误观点。

最后码字不易,觉得有帮助的朋友点赞、收藏、关注走一波。

猜你喜欢

转载自blog.csdn.net/forward_xx/article/details/127020639
今日推荐