怎么在Vue2中自定义一个图片懒加载指令(vue2,编程语言)

时间:2024-04-29 21:25:15 作者 : 石家庄SEO 分类 : 编程语言
  • TAG :

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 的代码如下:

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

1.3 使用到的 Web API

1.3.1 Element.clientHeight

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

  • 对于那些没有定义 CSS 或者内联布局盒子的元素,该 API 会返回 0;

  • 对于根元素(html 元素)或怪异模式下的 body 元素,该 API 将返回视口高度(不包含任何滚动条)

  • 其他情况,该 API 会返回元素内部的高度(以像素为单位),包含contentpadding,不包含bordermargin与水平滚动条(如果存在)。

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

示例图如下:

怎么在Vue2中自定义一个图片懒加载指令

1.3.2 Element.getBoundingClientRect()

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

  • width:就是元素自身宽度

  • height: 元素自身高度

  • left(x):元素开始位置到窗口左边的距离

  • right: 元素的右边到窗口左边的距离

  • bottom: 元素的下边到窗口上边的距离

  • top(y): 元素的上边到窗口上边的距离

  • x 和 y 相当于 left 和 top

示意图如下:

怎么在Vue2中自定义一个图片懒加载指令

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

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

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

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

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

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

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

使用 v-lazy 指令的示例代码如下:

<template>
<divclass="container">
<ul>
<liv-for="imginimgs":key="img.id">
<imgv-lazy="img.src":alt="img.alt":title="img.title"/>
</li>
</ul>
</div>
</template>
<script>
exportdefault{
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}已进行防抖的函数
*/
exportdefaultfunction(fn,duration=100){
lettimer=null;
return(...args)=>{
clearTimeout(timer);
timer=setTimeout(()=>{
fn(...args);
},duration);
};
}

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

importeventBusfrom"@/eventBus";//引入事件总线
import{debounce}from"@/utils";//引入函数防抖工具函数

//调用setImages函数,就可以处理那些符合条件的图片
functionsetImages(){}

//监听事件总线中的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该部分代码如下:

importeventBusfrom"@/eventBus";//引入事件总线
import{debounce}from"@/utils";//引入函数防抖工具函数

//调用setImages函数,就可以处理那些符合条件的图片
functionsetImages(){}

//监听事件总线中的mainScroll事件,该事件触发时调用setImages函数来加载图片
eventBus.$on("mainScroll",debounce(setImages,50));

//上面代码是3.1如何监听容器的滚动条的滚动?
//下面代码是3.2使用自定义指令哪些钩子函数?

letimgs=[];//存储收集到的的图片信息当图片加载好后删除该图片信息

//调用setImage函数,就可以进行单张图片的加载
functionsetImage(img){}

exportdefault{
inserted(el,bindings){
//刚插入父节点时收集img节点信息
constimg={
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 <= document.documentElement.clientHeight 时,该 img 元素在视口内

    • 反之则不在视口内

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

    • 当-img.getBoundingClientRect().top <= img.getBoundingClientRect().height 时,该 img 元素在视口内

    • 反之则不在视口内

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

importeventBusfrom"@/eventBus";//引入事件总线
import{debounce}from"@/utils";//引入函数防抖工具函数

letimgs=[];//存储收集到的的图片信息

//调用setImages函数,就可以处理那些符合条件的图片
functionsetImages(){
for(constimgofimgs){
setImage(img);//处理该图片
}
}

//监听事件总线中的mainScroll事件,该事件触发时调用setImages函数来加载符合条件图片
eventBus.$on("mainScroll",debounce(setImages,50));

//当图片加载好后删除该图片信息
exportdefault{
inserted(el,bindings){
//刚插入父节点时收集img节点信息
constimg={
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函数,就可以进行单张图片的加载
functionsetImage(img){
constclientHeight=document.documentElement.clientHeight;//视口高度
constrect=img.dom.getBoundingClientRect();//图片的位置信息
//取默认值150是为了解决图片未加载成功时高度缺失的问题
constheight=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完整的代码如下:

importeventBusfrom"@/eventBus";//引入事件总线
import{debounce}from"@/utils";//引入函数防抖工具函数
importdefaultGiffrom"@/assets/default.gif";//在assets静态文件夹下放入默认图

letimgs=[];//存储收集到的且未加载的图片信息

//调用setImage函数,就可以进行单张图片的加载
functionsetImage(img){
img.dom.src=defaultGif;//先暂时使用默认图片
constclientHeight=document.documentElement.clientHeight;//视口高度
constrect=img.dom.getBoundingClientRect();//图片的位置信息
//取默认值150是为了解决图片未加载成功时高度缺失的问题
constheight=rect.height||150;//图片的高度
//判断该图片是否在视口范围内
if(-rect.top<=height&&rect.top<=clientHeight){
//在视口范围内进行相关处理操作
consttempImg=newImage();//新建Image对象实例
//改写onload事件
tempImg.onload=function(){
//当图片加载完成之后
img.dom.src=img.src;//替换img元素的src属性
};
tempImg.src=img.src;
imgs=imgs.filter((i)=>i!==img);//将已加载好的图片进行删除
}
}

//调用setImages函数,就可以处理那些符合条件的图片
functionsetImages(){
for(constimgofimgs){
setImage(img);//处理该图片
}
}

//监听事件总线中的mainScroll事件,该事件触发时调用setImages函数来加载符合条件图片
eventBus.$on("mainScroll",debounce(setImages,50));

//当图片加载好后删除该图片信息
exportdefault{
inserted(el,bindings){
//刚插入父节点时收集img节点信息
constimg={
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中自定义一个图片懒加载指令的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:thinkphp如何获取当前访问的方法名下一篇:

24 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18