• 技术文章 >web前端 >Vue.js

    手把手带你在 Vue2 中自定义一个图片懒加载指令

    青灯夜游青灯夜游2022-09-22 20:29:48转载112
    Vue中怎么自定义图片懒加载指令?下面本篇文章带大家深入介绍Vue2中自定义图片懒加载指令“v-lazy”的方法,希望对大家有所帮助!

    大前端成长进阶课程:进入学习

    由于我在开发的个人博客前台页面时,想优化网站的响应速度,所以想实现图片懒加载效果。

    我是通过自定义指令v-lazy实现的,所以在这跟大家分享一下这个指令的开发流程及其难点的解决方法。【相关推荐:vuejs视频教程

    1.涉及到的主要知识讲解

    自定义图片懒加载指令主要涉及以下三块知识:

    下面我会对这些知识点进行一一介绍。

    1.1 Vue2 中自定义指令

    下面我只对自定义指令做简单的介绍,详细介绍大家可以参照Vue 官网 - 自定义指令

    1.1.1 指令对象的钩子函数

    钩子函数的参数主要有这四个el、binding、vnode、oldVnode

    1.1.2 钩子函数参数

    1.2 使用事件总线进行模块之间的通信

    对事件总线不熟悉的朋友,可以参照该博客什么是 Vue 事件总线(EventBus)

    我们可以借助 vue 示例来实现事件总线,也可以自行封装;我使用了第一种方法。

    因此事件总线配置文件---eventBus.js 的代码如下:

    import Vue from "vue";
    const eventBus = new Vue({});
    /*
     * 事件名:mainScroll
     * 含义:主区域滚动条位置变化后触发
     * 参数:
     * - 滚动的dom元素,如果是undefined,则表示dom元素已经不存在
     */
    //在Vue.prototype原型上注册事件总线,方便vue实例对象监听和触发
    Vue.prototype.$bus = eventBus;
    //导出事件总线,方便在其他js模块监听和触发事件总线上的事件
    export default eventBus;

    1.3 使用到的 Web API

    1.3.1 Element.clientHeight

    首先Element.clientHeight是一个只读属性,具有以下特点:

    另外改 API 会将获取的值四舍五入取整数。如果你需要小数结果,可以使用 element.getBoundingClientRect()方法。

    示例图如下:

    1.png

    该 API 的详细文档可参照MDN - Element.clientHeight

    1.3.2 Element.getBoundingClientRect()

    Element.getBoundingClientRect()方法返回一个DOMRect对象,其提供了元素的大小及其相对于视口的位置。 该方法无参数,返回值为DOMRect对象,该对象的属性以下几个:

    示意图如下:

    2.png

    该 API 的详细文档可以参照MDN - Element.getBoundingClientRect()

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

    2.1 最终的实现效果

    最终效果如下图:

    3.gif

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

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

    另外因为我使用事件总线这方法来自己通信,使用还需引入事件总线配置文件---eventBus.js

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

    import Vue from "vue";
    import App from "./App.vue";
    import "./eventBus"; //引入事件总线
    import vLazy from "./directives/lazy";
    Vue.directive("lazy", vLazy); //全局注册指令
    new Vue({
      render: (h) => h(App),
    }).$mount("#app");

    使用 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: "",
            },
          ],
        };
      },
      //下面的代码可以用组件混入来进行封装,从而优化代码结构
      methods: {
        //触发mainScroll事件
        handleMainScroll() {
          this.$bus.$emit("mainScroll", this.$refs.container);
        },
      },
      mounted() {
        //监听滚轮事件
        this.$refs.container.addEventListener("scroll", this.handleMainScroll);
      },
      beforeDestroy() {
        this.$bus.$emit("mainScroll");//参数传入undefined,表示dom元素已经不存在
        //取消监听滚轮事件
        this.$refs.container.removeEventListener("scroll", this.handleMainScroll);
      },
    
    };
    </script>

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

    要实现图片懒加载效果,我们首先要思考以下四个关键问题:

    3.1 如何监听容器的滚动条的滚动?

    对于这问题,由于我的博客系统在处理其他组件之间的传值问题时,使用了事件总线方法,所以为了方便,我也使用这一方法,当然大家可以针对实际场景使用其他方法来解决这问题。

    所以我们要在 v-lazy 图片懒加载指令配置文件---lazy.js文件中监听事件总线 eventBus 中的mainScroll事件,同时为了性能优化,我们需要进行 mainScroll 事件的事件防抖

    其中事件防抖工具函数---debounce.js代码如下:

    /**
     * @param {Function} fn 需要进行防抖操作的事件函数
     * @param {Number} duration 间隔时间
     * @returns {Function} 已进行防抖的函数
     */
    export default function (fn, duration = 100) {
      let timer = null;
      return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => {
          fn(...args);
        }, duration);
      };
    }

    图片懒加载指令配置文件---lazy.js该部分代码如下:

    import eventBus from "@/eventBus"; //引入事件总线
    import { debounce } from "@/utils"; //引入函数防抖工具函数
    
    // 调用setImages函数,就可以处理那些符合条件的图片
    function setImages() {}
    
    //监听事件总线中的mainScroll事件,该事件触发时调用setImages函数来加载图片
    eventBus.$on("mainScroll", debounce(setImages, 50));

    3.2 使用自定义指令哪些钩子函数?

    经过场景分析,我选用了insertedunbind这两个钩子函数,当 img 元素刚插入父节点时收集 img 的信息,并在内部使用一个 imgs 数组存储已收集到的信息,当指令与元素解绑时,进行 imgs 数组清空操作。

    另外我们还需获取图片 img 元素的 DOM 节点和 src 属性值

    所以此时图片懒加载指令配置文件---lazy.js该部分代码如下:

    import eventBus from "@/eventBus"; //引入事件总线
    import { debounce } from "@/utils"; //引入函数防抖工具函数
    
    // 调用setImages函数,就可以处理那些符合条件的图片
    function setImages() {}
    
    //监听事件总线中的mainScroll事件,该事件触发时调用setImages函数来加载图片
    eventBus.$on("mainScroll", debounce(setImages, 50));
    
    //上面代码是3.1 如何监听容器的滚动条的滚动?
    //下面代码是3.2 使用自定义指令哪些钩子函数?
    
    let imgs = []; //存储收集到的的图片信息 当图片加载好后删除该图片信息
    
    //调用setImage函数,就可以进行单张图片的加载
    function setImage(img) {}
    
    export default {
      inserted(el, bindings) {
        //刚插入父节点时 收集img节点信息
        const img = {
          dom: el, //img 元素DOM节点
          src: bindings.value, //img的src属性值
        };
        imgs.push(img); //先将图片信息存储到imgs数组
        setImage(img); // 立即判断该图片是否要加载
      },
      unbind(el) {
        //解绑时 删除 imgs 中的所有图片信息
        imgs = imgs.filter((img) => img.dom !== el);
      },
    };

    3.3 如何判断图片 img 元素是否在用户的可见范围内?

    对于上面这问题,我们先进行问题拆分:

    1、获得用户的可见范围(视口)

    2、获得图片 img 元素的位置信息

    3、判断图片 img 元素是否在视口内

    图片懒加载指令配置文件---lazy.js该部分代码如下:

    import eventBus from "@/eventBus"; //引入事件总线
    import { debounce } from "@/utils"; //引入函数防抖工具函数
    
    let imgs = []; //存储收集到的的图片信息
    
    // 调用setImages函数,就可以处理那些符合条件的图片
    function setImages() {
      for (const img of imgs) {
        setImage(img); // 处理该图片
      }
    }
    
    //监听事件总线中的mainScroll事件,该事件触发时调用setImages函数来加载符合条件图片
    eventBus.$on("mainScroll", debounce(setImages, 50));
    
    //当图片加载好后删除该图片信息
    export default {
      inserted(el, bindings) {
        //刚插入父节点时 收集img节点信息
        const img = {
          dom: el, //img 元素DOM节点
          src: bindings.value, //img的src属性值
        };
        imgs.push(img); //先将图片信息存储到imgs数组
        setImage(img); // 立即判断该图片是否要加载
      },
      unbind(el) {
        //解绑时 删除 imgs 中的所有图片信息
        imgs = imgs.filter((img) => img.dom !== el);
      },
    };
    
    //上面代码是3.1 如何监听容器的滚动条的滚动?+ 3.2 使用自定义指令哪些钩子函数?
    //下面代码是3.3 如何判断图片 img 元素是否在用户的可见范围内?
    
    //调用setImage函数,就可以进行单张图片的加载
    function setImage(img) {
      const clientHeight = document.documentElement.clientHeight; //视口高度
      const rect = img.dom.getBoundingClientRect(); //图片的位置信息
      //取默认值150 是为了解决图片未加载成功时高度缺失的问题
      const height = rect.height || 150; //图片的高度
    
      // 判断该图片是否在视口范围内
      if (rect.top >= -height && rect.top <= clientHeight) {
        // 在视口范围内 进行相关处理操作
      } else {
        // 不在视口范围内 不进行操作
      }
    }

    3.4 如何处理图片 img 元素的加载?

    由效果图我们可看出一开始所有 img 元素都是一张默认的 GIF 图片---defaultGif,等该 img 元素进入到视口范围时,开始加载该图片,加载完成后再进行替换。

    这里我还进行一个优化操作,就是先新建一个 Image 对象实例,代替 img 元素加载图片,因为图片加载完成后会触发onload事件,所以我们只需对onload事件进行改写,在其内部执行 img 元素的 src 属性替换操作,这样就解决了加载过程中图片空白的情况。

    所以图片懒加载指令配置文件---lazy.js完整的代码如下:

    import eventBus from "@/eventBus"; //引入事件总线
    import { debounce } from "@/utils"; //引入函数防抖工具函数
    import defaultGif from "@/assets/default.gif"; //在assets静态文件夹下放入默认图
    
    let imgs = []; //存储收集到的且未加载的图片信息
    
    //调用setImage函数,就可以进行单张图片的加载
    function setImage(img) {
      img.dom.src = defaultGif; // 先暂时使用默认图片
      const clientHeight = document.documentElement.clientHeight; //视口高度
      const rect = img.dom.getBoundingClientRect(); //图片的位置信息
      //取默认值150 是为了解决图片未加载成功时 高度缺失的问题
      const height = rect.height || 150; //图片的高度
      // 判断该图片是否在视口范围内
      if (-rect.top <= height && rect.top <= clientHeight) {
        // 在视口范围内 进行相关处理操作
        const tempImg = new Image(); //新建Image对象实例
        //改写onload事件
        tempImg.onload = function () {
          // 当图片加载完成之后
          img.dom.src = img.src; //替换img元素的src属性
        };
        tempImg.src = img.src;
        imgs = imgs.filter((i) => i !== img); //将已加载好的图片进行删除
      }
    }
    
    // 调用setImages函数,就可以处理那些符合条件的图片
    function setImages() {
      for (const img of imgs) {
        setImage(img); // 处理该图片
      }
    }
    
    //监听事件总线中的mainScroll事件,该事件触发时调用setImages函数来加载符合条件图片
    eventBus.$on("mainScroll", debounce(setImages, 50));
    
    //当图片加载好后删除该图片信息
    export default {
      inserted(el, bindings) {
        //刚插入父节点时 收集img节点信息
        const img = {
          dom: el, //img 元素DOM节点
          src: bindings.value, //img的src属性值
        };
        imgs.push(img); //先将图片信息存储到imgs数组
        setImage(img); // 立即判断该图片是否要加载
      },
      unbind(el) {
        //解绑时 清空 imgs
        imgs = imgs.filter((img) => img.dom !== el);
      },
    };

    (学习视频分享:web前端开发编程基础视频

    以上就是手把手带你在 Vue2 中自定义一个图片懒加载指令的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    专题推荐:vue3 Vue vue.js
    上一篇:聊聊怎么Vue中避免在动态绑定类时出现空类的情况! 下一篇:vue不能用index做为唯一标识的原因浅析
    VIP课程(WEB全栈开发)

    相关文章推荐

    • ❤️‍🔥共22门课程,总价3725元,会员免费学• 9个vue3开发技巧,提升效率帮助你早点下班!• 聊聊vue3中的name属性,看看怎么使用!• 一文聊聊Vue响应式实现原理• 聊聊vue+antv怎么实现数据可视化图表• 一文深入剖析Vue3中的响应式机制
    1/1

    PHP中文网