怎么在Vue2中自定义一个图片懒加载指令
这篇文章主要讲解了“怎么在Vue2中自定义一个图片懒加载指令”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么在Vue2中自定义一个图片懒加载指令”吧!
创新互联主要从事成都网站设计、成都做网站、网页设计、企业做网站、公司建网站等业务。立足成都服务黎平,十载网站建设经验,价格优惠、服务专业,欢迎来电咨询建站服务:13518219792
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 会返回元素内部的高度(以像素为单位),包含
content
与padding
,不包含border
、margin
与水平滚动条(如果存在)。
另外改 API 会将获取的值四舍五入取整数。如果你需要小数结果,可以使用 element.getBoundingClientRect()方法。
示例图如下:
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.图片懒加载指令的基本介绍
图片懒加载指令的注册与使用
由于在个人博客系统中图片懒加载指令使用的比较频繁,使用我选择了全局注册该指令。
另外因为我使用事件总线这方法来自己通信,使用还需引入事件总线配置文件---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 指令的示例代码如下:
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 使用自定义指令哪些钩子函数?
经过场景分析,我选用了inserted
和unbind
这两个钩子函数,当 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 <= 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中自定义一个图片懒加载指令”的内容了,经过本文的学习后,相信大家对怎么在Vue2中自定义一个图片懒加载指令这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是创新互联,小编将为大家推送更多相关知识点的文章,欢迎关注!
网站名称:怎么在Vue2中自定义一个图片懒加载指令
网站地址:http://azwzsj.com/article/picipp.html