Home >Web Front-end >Vue.js >Take you step by step to customize an image lazy loading instruction in Vue2

Take you step by step to customize an image lazy loading instruction in Vue2

青灯夜游
青灯夜游forward
2022-09-22 20:29:482217browse

How to customize the image lazy loading instruction in

Vue? The following article will give you an in-depth introduction to the custom image lazy loading instruction "v-lazy" in Vue2. I hope it will be helpful to you!

Take you step by step to customize an image lazy loading instruction in Vue2

When I was developing the front-end page of my personal blog, I wanted to optimize the response speed of the website, so I wanted to achieve the lazy loading effect of images.

I implemented it through the custom command v-lazy, so I would like to share with you the development process of this command and the solutions to its difficulties. [Related recommendations: vuejs video tutorial]

1. Explanation of the main knowledge involved

The custom image lazy loading instructions mainly involve the following three Block knowledge:

  • Custom instructions in Vue2
  • Use event bus for communication between modules
  • Web API used
    • Element .clientHeight
    • Element.getBoundingClientRect()

I will introduce these knowledge points one by one below.

1.1 Custom instructions in Vue2

I will only briefly introduce the custom instructions below. For detailed introduction, you can refer toVue official website-custom instructions .

1.1.1 Hook function of the instruction object

  • bind: Called only once, when the instruction is bound to an element for the first time. One-time initialization settings can be performed here.
  • inserted: Called when the bound element is inserted into the parent node (only the parent node is guaranteed to exist, but not necessarily inserted into the document).
  • update: Called when the VNode of the component is updated, but may occur before its child VNode is updated. The value of the directive may or may not have changed. Unnecessary template updates can be ignored by comparing the values ​​before and after the update (see details of hook function parameters below).
  • componentUpdated: Called after all the VNode of the component where the instruction is located and its sub-VNodes have been updated.
  • unbind: Called only once, when the instruction is unbound from the element.

The parameters of the hook function mainly include these four el, binding, vnode, oldVnode.

1.1.2 Hook function parameters

  • el: The element bound to the instruction can be used to directly operate the DOM.
  • binding: An object containing the following properties:
    • name: Instruction name, excluding the v- prefix.
    • value: The binding value of the directive, such as: v-my-directive="1 1", the binding value is 2.
    • oldValue: The previous value of the instruction binding, only available in update and componentUpdated hooks. Available regardless of whether the value has changed.
    • expression: Instruction expression in string form. For example, in v-my-directive="1 1", the expression is "1 1".
    • arg: Parameters passed to the command, optional. For example, in v-my-directive:foo, the parameter is "foo".
    • modifiers: An object containing modifiers. For example: in v-my-directive.foo.bar, the modifier object is { foo: true, bar: true }.
  • vnode: The virtual node generated by Vue compilation. Move to VNode API to learn more details.
  • oldVnode: The previous virtual node, only available in update and componentUpdated hooks.

1.2 Use the event bus for communication between modules

Friends who are not familiar with the event bus can refer to this blogWhat is a Vue event Bus(EventBus).

  • Listen to events on the event bus---call the $on method
  • Trigger events on the event bus---call the $emit method
  • Cancel listening events Events on the bus --- call the $off method

We can use the vue example to implement the event bus, or we can encapsulate it ourselves; I used the first method.

So the code of the event bus configuration file ---eventBus.js is as follows:

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 used

1.3.1 Element.clientHeight

First of all Element.clientHeight is a read-only property with the following characteristics:

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

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

示例图如下:

Take you step by step to customize an image lazy loading instruction in Vue2

该 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

示意图如下:

Take you step by step to customize an image lazy loading instruction in Vue2

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

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

2.1 最终的实现效果

最终效果如下图:

Take you step by step to customize an image lazy loading instruction in Vue2

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>
    <ul>
      <li>
        <img  alt="Take you step by step to customize an image lazy loading instruction in Vue2" >
      </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) => img.dom !== el);
  },
};

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

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

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

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

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

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

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

  • img.getBoundingClientRect().top > 0 时,说明图片在视口内或视口下方
    • 当 img.getBoundingClientRect().top
    • 反之则不在视口内
  • img.getBoundingClientRect().top
  • 当-img.getBoundingClientRect().top
  • 反之则不在视口内

图片懒加载指令配置文件---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 <h3 data-id="heading-16"><strong>3.4 如何处理图片 img 元素的加载?</strong></h3><p>由效果图我们可看出一开始所有 img 元素都是一张默认的 GIF 图片---<code>defaultGif</code>,等该 img 元素进入到视口范围时,开始加载该图片,加载完成后再进行替换。</p><p>这里我还进行一个优化操作,就是先新建一个 <code>Image 对象实例</code>,代替 img 元素加载图片,因为图片加载完成后会触发<code>onload事件</code>,所以我们只需对<code>onload事件</code>进行改写,在其内部执行 img 元素的 src 属性替换操作,这样就解决了加载过程中图片空白的情况。</p><p>所以图片懒加载指令配置文件---<code>lazy.js</code>完整的代码如下:</p><pre class="brush:php;toolbar:false">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  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前端开发编程基础视频

The above is the detailed content of Take you step by step to customize an image lazy loading instruction in Vue2. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.cn. If there is any infringement, please contact admin@php.cn delete