Vue2 中自定义图片懒加载指令 v-lazy实例详解

目录
  • 引言
  • 1.涉及到的主要知识讲解
    • 1.1 Vue2 中自定义指令
      • 1.1.1 指令对象的钩子函数
      • 1.1.2 钩子函数参数
    • 1.2 使用事件总线进行模块之间的通信
    • 1.3 使用到的 Web API
      • 1.3.1 Element.clientHeight
      • 1.3.2 Element.getBoundingClientRect()
  • 2.图片懒加载指令的基本介绍
    • 2.1 最终的实现效果
    • 2.2 图片懒加载指令的注册与使用
  • 3. 实现图片懒加载的原理
    • 3.1 如何监听容器的滚动条的滚动?
    • 3.2 使用自定义指令哪些钩子函数?
    • 3.3 如何判断图片 img 元素是否在用户的可见范围内?
    • 3.4 如何处理图片 img 元素的加载?

引言

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

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

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

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

  • Vue2 中自定义指令
  • 使用事件总线进行模块之间的通信
  • 使用到的 Web API
    • Element.clientHeight
    • Element.getBoundingClientRect()

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

1.1 Vue2 中自定义指令

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

1.1.1 指令对象的钩子函数

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。可通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。

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

1.1.2 钩子函数参数

  • el:指令所绑定的元素,可以用来直接操作 DOM。
  • binding:一个对象,包含以下 property:
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,如:v-my-directive="1 + 1" 中,绑定值为 2。
    • oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
  • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

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

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

  • 监听事件总线上的事件---调用 $on 方法
  • 触发事件总线上的事件---调用 $emit 方法
  • 取消监听事件总线上的事件---调用 $off 方法

我们可以借助 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是一个只读属性,具有以下特点:

  • 对于那些没有定义 CSS 或者内联布局盒子的元素,该 API 会返回 0;
  • 对于根元素(html 元素)或怪异模式下的 body 元素,该 API 将返回视口高度(不包含任何滚动条)
  • 其他情况,该 API 会返回元素内部的高度(以像素为单位),包含contentpadding,不包含bordermargin与水平滚动条(如果存在)。

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

示例图如下:

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

1.3.2 Element.getBoundingClientRect()

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

  • width:就是元素自身宽度
  • height: 元素自身高度
  • left(x):元素开始位置到窗口左边的距离
  • right: 元素的右边到窗口左边的距离
  • bottom: 元素的下边到窗口上边的距离
  • top(y): 元素的上边到窗口上边的距离
  • x 和 y 相当于 left 和 top

示意图如下:

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

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

2.1 最终的实现效果

最终效果如下图:

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. 实现图片懒加载的原理

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

  • 如何监听容器的滚动条的滚动?
  • 使用自定义指令哪些钩子函数?
  • 如何判断图片 img 元素是否在用户的可见范围内?
  • 如何处理图片 img 元素的加载?

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 属性值

  • 由于我们将指令绑定到了 img'元素上,所以可通过自定义指令钩子函树中的el参数得到其 DOM 节点
  • 由于我们将 src 值传给了指令,所以可通过bindings.value参数得到其 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) =&gt; img.dom !== el);
  },
};

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

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

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

由于我的博客系统只需考虑视口高度,所以我只使用了Element.clientHeight 这 API。(如果还需要考虑宽度就再使用Element.clientWidth)

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

我使用了Element.getBoundingClientRect()这 API。

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

img.getBoundingClientRect().top > 0 时,说明图片在视口内或视口下方

  • 当 img.getBoundingClientRect().top <= document.documentElement.clientHeight 时,该 img 元素在视口内
  • 反之则不在视口内

img.getBoundingClientRect().top < 0 时,说明图片在视口内或视口上方

  • 当-img.getBoundingClientRect().top <= img.getBoundingClientRect().height 时,该 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);
  },
};

以上就是Vue2 中自定义图片懒加载指令 v-lazy实例详解的详细内容,更多关于Vue2自定义v-lazy指令的资料请关注我们其它相关文章!

(0)

相关推荐

  • vue2从数据变化到视图变化发布订阅模式详解

    目录 引言 一.发布订阅者模式的特点 二.vue中的发布订阅者模式 1.dep 2.Object.defineProperty 3.watcher 4.dep.depend 5.dep.notify 6.订阅者取消订阅 小结 引言 发布订阅者模式是最常见的模式之一,它是一种一对多的对应关系,当一个对象发生变化时会通知依赖他的对象,接受到通知的对象会根据情况执行自己的行为. 假设有财经报纸送报员financialDep,有报纸阅读爱好者a,b,c,那么a,b,c想订报纸就告诉financialDe

  • vue2 d3实现企查查股权穿透图股权结构图效果详解

    目录 前言 最终效果: 版本信息: 股权穿透图基础功能: 股权结构图基础功能: 股权穿透图代码: 股权结构图代码: 总结: 前言 vue3 框架中使用vue2代码结合d3完成股权穿透图和股权结构图(h5) (没错听上去很违规,但我懒得把代码从vue2改成vue3了,所以是在vue3框架里用vue2写法完成的) 最终效果: 版本信息: "d3": "4.13.0", "vant": "^3.1.5", "vue&quo

  • vue2从数据到视图渲染之模板渲染详解

    目录 引言 1.从new Vue()入口开始: 2.this._init 3.挂载函数vm.$mount(vm.$options.el) 4.mountComponent函数 5._render函数 6.createElement函数返回虚拟DOM 7._update函数 8.patch函数 9.createElm函数 10.移除 引言 一般使用html,css和javascript直接将数据渲染到视图中,需要关心如何去操作dom,如果数据量比较大,那么操作过程将会繁琐且不好控制.vue将这些操

  • vue2从数据变化到视图变化之diff算法图文详解

    目录 引言 1.isUndef(oldStartVnode) 2.isUndef(oldEndVnode) 3.sameVnode(oldStartVnode, newStartVnode) 4.sameVnode(oldEndVnode, newEndVnode) 5.sameVnode(oldStartVnode, newEndVnode) 6.sameVnode(oldEndVnode, newStartVnode) 7.如果以上都不满足 小结 引言 vue数据的渲染会引入视图的重新渲染.

  • vue2从数据变化到视图变化之nextTick使用详解

    目录 引言 1.vue中nextTick的使用场景 2.vue中nextTick在哪里定义 3.vue中nextTick的实现原理 (1)callbacks的定义 (2)timerFunc的定义 (3)cb未传入的处理 4.vue中nextTick的任务分类 小结 引言 nextTick在vue中是一个很重要的方法,在new Vue实例化的同步过程中,将一些需要异步处理的函数推到异步队列中去,可以等new Vue所有的同步任务执行完后,再执行异步队列中的函数. 1.vue中nextTick的使用

  • Vue自定义图片懒加载指令v-lazyload详解

    Vue是可以自定义指令的,最近学习过程中遇见了一个需要图片懒加载的功能,最后参考了别人的代码和思路自己重新写了一遍.以下将详细介绍如何实现自定义指令v-lazyload. 先看如何使用这个指令: <img v-lazyload="imageSrc" > imageSrc是要加载的图片的实际路径. 为了实现这个指令,我们首先单独建立一个文件,名字为lazyload.js.并填写基本的代码,如下: //Vue 图片懒加载,导出模块 export default (Vue , o

  • 浅谈vue中使用图片懒加载vue-lazyload插件详细指南

    在vue中使用图片懒加载详细指南,分享给大家.具体如下: 说明 当网络请求比较慢的时候,提前给这张图片添加一个像素比较低的占位图片,不至于堆叠在一块,或显示大片空白,让用户体验更好一点. 使用方式 使用vue的 vue-lazyload 插件 插件地址:https://www.npmjs.com/package/vue-lazyload 案例 demo: 懒加载案例demo Installation 安装方式 npm $ npm i vue-lazyload -D CDN CDN: https:

  • PyTorch加载自己的数据集实例详解

    数据预处理在解决深度学习问题的过程中,往往需要花费大量的时间和精力. 数据处理的质量对训练神经网络来说十分重要,良好的数据处理不仅会加速模型训练, 更会提高模型性能.为解决这一问题,PyTorch提供了几个高效便捷的工具, 以便使用者进行数据处理或增强等操作,同时可通过并行化加速数据加载. 数据集存放大致有以下两种方式: (1)所有数据集放在一个目录下,文件名上附有标签名,数据集存放格式如下: root/cat_dog/cat.01.jpg root/cat_dog/cat.02.jpg ...

  • 微信小程序JS加载esmap地图的实例详解

    一.在微信小程序里显示室内三维地图 需要满足的两个条件 调用ESMap室内地图需要用到小程序web-view组件,想要通过 web-view 调用ESMap室内地图需要满足以下 2 个条件: 1. 小程序是企业主体,微信 web-view 组件不对个人类型的小程序开放. 2. 您需要有一个自己的域名,在嵌入网页的时候需要在微信后台验证域名(只有自己域名下的网页才能被正确地显示哦,不能随便找一个公开链接). 二.具体实现步骤 1.域名验证: 由于微信平台的规定,web-view 指向的地址,必须是

  • Javarscript中模块(module)、加载(load)与捆绑(bundle)详解

    JS模块简介 js模块化,简单说就是将系统或者功能分隔成单独的.互不影响的代码片段,经过严格定义接口,使各模块间互不影响,且可以为其他所用. 常见的模块化有,C中的include (.h)文件.java中的import等. 为什么JS需要模块 很显然,没有模块我们也可以实现同样的功能,为什么我们还要使用模块来写js代码呢?下面几点是模块化给我们带来的一些变化: 抽象代码:我们在使用模块来调用一个api时,可以不用知道内部是如何实现的,避免去理解其中复杂的代码: 封装代码:在不需要再次修改代码的前

  • js前端实现图片懒加载(lazyload)的两种方式

    在实际的项目开发中,我们通常会遇见这样的场景:一个页面有很多图片,而首屏出现的图片大概就一两张,那么我们还要一次性把所有图片都加载出来吗?显然这是愚蠢的,不仅影响页面渲染速度,还浪费带宽.这也就是们通常所说的首屏加载,技术上现实其中要用的技术就是图片懒加载--到可视区域再加载. 思路: 将页面里所有img属性src属性用data-xx代替,当页面滚动直至此图片出现在可视区域时,用js取到该图片的data-xx的值赋给src. 关于各种宽高: 页可见区域宽: document.body.clien

  • vue项目中图片懒加载时出现的问题及解决

    目录 vue图片懒加载的问题 vue图片懒加载实现步骤 vue图片懒加载踩过的坑 今天踩过的坑总结 vue图片懒加载的问题 项目中遇到一个问题,记录一下,vue项目中前期没有做图片懒加载的时候,当图片出现错误或者显示路径不对,我加了onerror事件进行错误监听并添加一张默认的图片,优化用户体验. 后期因为图片数量变多,所以加入了图片懒加载,但在懒加载中挂载时只加了loading的图片,没有加error,所以导致页面图片未正常加载的地方出现默认图片闪烁的现象,最后在挂载时加入error引入一张默

  • Vue实现一个图片懒加载插件

    前言 图片懒加载是一个很常用的功能,特别是一些电商平台,这对性能优化至关重要.今天就用vue来实现一个图片懒加载的插件. 这篇博客采用"三步走"战略--Vue.use().Vue.direction.Vue图片懒加载插件实现,逐步实现一个Vue的图片懒加载插件. Vue.use() 就像开发jQuery插件要用$.fn.extent()一样,开发Vue插件我们要用Vue.use().其实就是官方内部实现的一个方法,供广大开发者灵活开发属于自己的插件.只需要按照约定好的规则开发就行. 用

  • vue3+typescript实现图片懒加载插件

    github项目地址: github.com/murongg/vue- 求star 与 issues 我文采不好,可能写的文章不咋样,有什么问题可以在留言区评论,我会尽力解答 本项目已经发布到npm 安装: $ npm i vue3-lazyload # or $ yarn add vue3-lazyload 需求分析 支持自定义 loading 图片,图片加载状态时使用此图片 支持自定义 error 图片,图片加载失败后使用此图片 支持 lifecycle hooks,类似于 vue 的生命周

  • JavaScript如何实现图片懒加载(lazyload) 提高用户体验(增强版)

    目录: 懒加载的意义(为什么要使用懒加载) 原理 代码 在上篇文章给大家介绍了JavaScript实现图片懒加载(Lazyload),大家可以参考下. 懒加载的意义(为什么要使用懒加载) 对页面加载速度影响最大的就是图片,一张普通的图片可以达到几M的大小,而代码也许就只有几十KB.当页面图片很多时,页面的加载速度缓慢,几S钟内页面没有加载完成,也许会失去很多的用户. 所以,对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区

随机推荐