Vue2 中自定义图片懒加载指令 2.0
由于我前天分享的Vue2 中自定义图片懒加载指令数据很好,所以我在这里继续分享第二种实现方法 — 使用Intersection Observer API
。
这种方法实现与使用起来都更简单,性能也更优,而唯一缺点就是兼容性没这么好,但也基本能兼容市面上的大部分浏览器(IE 除外),相信后面兼容性肯定会越来越好的,所以大家可以提前学起来哦。
对该 API 不熟悉的朋友可以去看看我的这篇大白话详解 Intersection Observer API博客。
没看过我前天分享的Vue2 中自定义图片懒加载指令博客的朋友,也强烈推荐去看看哦。
2.图片懒加载指令的基本介绍
2.1 最终的实现效果
最终效果如下图:
2.2 图片懒加载指令的注册与使用
由于在个人博客系统中图片懒加载指令使用的比较频繁,所以我依旧选择了全局注册该指令。
因为Intersection Observer API
内部会与异步检测目标元素与根元素相交情况的变化。所以我们就不用再使用事件总线来进行通信了,所以也就不需引入事件总线配置文件。
main.js入口文件
的代码如下:
import Vue from "vue";
import App from "./App.vue";
import vLazy from "./directives/lazy";
Vue.directive("lazy", vLazy); //全局注册指令
new Vue({
render: (h) => h(App),
}).$mount("#app");
由于不用事件总线进行通信,使用起来及其方便,只需将 img 图片元素的 src 属性绑定到 v-lazy 自定义指令上即可。
所以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: "",
},
],
};
},
};
</script>
2. 实现图片懒加载的原理
对Intersection Observer API
不熟悉的朋友可以去看看我的这篇大白话详解 Intersection Observer API博客。(写这篇博客花了很多心思,还画了许多图哦)
2.1 与上篇博客实现原理的对比
在上篇博客中,我提出以下四个问题,并进行解答,概括起来如下:
- 如何监听容器的滚动条的滚动?
- 当时我们使用了
事件总线
方法来监听父容器元素的滚轮事件,并使用了事件防抖
工具函数进行了性能优化。
- 当时我们使用了
- 使用自定义指令哪些钩子函数?
- 当时我们是使用
inserted
和unbind
这两个钩子函数,并使用 imgs 数组收集 img 的 DOM 与 src 信息。
- 当时我们是使用
- 如何判断图片 img 元素是否在用户的可见范围内?
- 当时我们是使用
Element.clientWidth
和Element.getBoundingClientRect()
这两个 API,并对 API 获得的信息进行了逻辑判断。
- 当时我们是使用
- 如何处理图片 img 元素的加载?
- 当时我们是用
Image 对象实例
代替 img 目标元素加载图片,并对Image 对象
的onload事件
进行了改写,在其内部执行 img 目标元素的 src 属性替换操作。
- 当时我们是用
而在上篇博客中,对于 问题 2 与 问题 4 的解决办法几乎没变,而 问题 1 与 问题 3 这=就可以直接使用Intersection Observer API
轻松解决,具体如下:
- 如何监听容器的滚动条的滚动?
- 我们只需将
IntersectionObserver对像
的根元素设为视口即可。(不传 root 属性与 threshold 属性)
- 我们只需将
- 使用自定义指令哪些钩子函数?
- 当时我们是使用
inserted
和unbind
这两个钩子函数,并使用 imgsInf 数组收集 img 的 DOM 与 src 信息。(不变,只进行了变量名更改)
- 当时我们是使用
- 如何判断图片 img 元素是否在用户的可见范围内?
- 我们只需使用
IntersectionObserverEntry对象
的isIntersecting
属性进行判断即可。
- 我们只需使用
- 如何处理图片 img 元素的加载?
- 当时我们是用
Image 对象实例
代替 img 目标元素加载图片,并对Image 对象
的onload事件
进行了改写,在其内部执行 img 目标元素的 src 属性替换操作。(不变,只是进行了函数封装)
- 当时我们是用
两篇博客的详细对比见下表:
上篇博客的解决办法 | 这篇博客的解决办法 | |
---|---|---|
如何监听容器的滚动条的滚动? | 事件总线 + 函数防抖 | 将IntersectionObserver对像 的root属性 设为视口 |
使用自定义指令哪些钩子函数? | inserted + unbind | 与上篇博客一致,只进行了变量名更改 |
如何判断图片 img 元素是否在用户的可见范围内? | clientWidth + getBoundingClientRect() | IntersectionObserverEntry对象 的isIntersecting属性 |
如何处理图片 img 元素的加载? | 用 Image 对象实例代替加载 | 与上篇博客一致,只进行了函数封装 |
大家就这么看起来应该都知道Intersection Observer API
的方法的实现更简单了。
2.2 实现步骤
- 我们先把解决问题 2 与问题 4 的代码直接写出来:
import defaultGif from "@/assets/default.gif"; //在assets静态文件夹下放入默认图
let imgsInf = []; //存储尚未加载的图片信息
//调用该函数 用于加载单张图片
function loadingImg(imgDOM) {
//获得该img元素的src信息
let imgSrc = imgsInf.filter((imgInf) => imgInf.dom === imgDOM)[0].src;
const tempImg = new Image(); //新建Image对象实例
//改写onload事件
tempImg.onload = function () {
// 当图片加载完成之后 替换img元素的src属性
imgDOM.src = imgSrc;
};
tempImg.src = imgSrc; //Image实例代替加载
imgsInf = imgsInf.filter((imgInf) => imgInf.dom !== imgDOM); //将已加载好的图片进行删除
}
export default {
inserted(el, bindings) {
//刚插入父节点时
el.src = defaultGif; // 先暂时使用默认图片的src
const imgInf = {
dom: el, //img 元素DOM节点
src: bindings.value, //img的真正src信息
};
imgsInf.push(imgInf); //存储img元素的src信息
},
unbind(el) {
//解绑时 清空 imgs
imgsInf = imgsInf.filter((imgInf) => imgInf.dom !== el);
},
};
- 创建
IntersectionObserver对象
并进行相应的配置,代码如下:
//io 为 IntersectionObserver对象 - 由IntersectionObserver()构造器创建
const io = new IntersectionObserver((entries) => {
//entries 为 IntersectionObserverEntry对像数组
entries.forEach((item) => {
//item为 IntersectionObserverEntry 对象
// isIntersecting是一个Boolean值,判断目标元素当前是否可见
if (item.isIntersecting) {
//img元素可见时
loadingImg(item.target); //加载该img元素
io.unobserve(item.target); //图片加载完后即停止监听该元素
}
});
}); //不传options参数
//默认根元素(root属性)为浏览器视口、
//默认阈值(threshold属性)为 0,表示目标元素刚进入根元素可见范围时触发回调函数
- 在合适的位置使用
IntersectionObserver对象
的observe
开始监听方法与unobserve
停止监听方法,所以整体代码如下:
import defaultGif from "@/assets/default.gif"; //在assets静态文件夹下放入默认图
let imgsInf = []; //存储尚未加载的图片信息
//调用该函数 用于加载单张图片
function loadingImg(imgDOM) {
//获得该img元素的src信息
let imgSrc = imgsInf.filter((imgInf) => imgInf.dom === imgDOM)[0].src;
const tempImg = new Image(); //新建Image对象实例
//改写onload事件
tempImg.onload = function () {
// 当图片加载完成之后 替换img元素的src属性
imgDOM.src = imgSrc;
};
tempImg.src = imgSrc; //Image实例代替加载
imgsInf = imgsInf.filter((imgInf) => imgInf.dom !== imgDOM); //将已加载好的图片进行删除
}
//io 为 IntersectionObserver对象 - 由IntersectionObserver()构造器创建
const io = new IntersectionObserver((entries) => {
//entries 为 IntersectionObserverEntry对像数组
entries.forEach((item) => {
//item为 IntersectionObserverEntry 对象
// isIntersecting是一个Boolean值,判断目标元素当前是否可见
if (item.isIntersecting) {
//img元素可见时
loadingImg(item.target); //加载该img元素
io.unobserve(item.target); //图片加载完后即停止监听该元素
}
});
}); //不传options参数
//默认根元素(root属性)为浏览器视口、
//默认阈值(threshold属性)为 0,表示目标元素刚进入根元素可见范围时触发回调函数
export default {
inserted(el, bindings) {
//刚插入父节点时
el.src = defaultGif; // 先暂时使用默认图片的src
io.observe(el); //开始监听该img元素
const imgInf = {
dom: el, //img 元素DOM节点
src: bindings.value, //img的真正src信息
};
imgsInf.push(imgInf); //存储img元素的src信息
},
unbind(el) {
//某个img元素解绑时 清除该img元素的信息 并停止监听该img元素
io.unobserve(el); //停止监听
imgsInf = imgsInf.filter((imgInf) => imgInf.dom !== el); //清除存储信息
},
};
这方法的逻辑判断比上篇博客的方法简单的多,所以学会了一定会给我们开发带来便利。
3. Intersection Observer API 的兼容性
Intersection Observer API
的兼容性如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l7VE7frt-1663962746438)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/562af71865f84020be86acefe260b263~tplv-k3u1fbpfcp-watermark.image?)]
大家在实际项目中选择性使用哦。
详情大家可参考CAN I USE - intersectionobserver。
参考博客
结语
这是我目前所了解的知识面中最好的解答,当然也有可能存在一定的误区。
所以如果对本文存在疑惑,可以在评论区留言,我会及时回复的,欢迎大家指出文中的错误观点。
最后码字不易,觉得有帮助的朋友点赞、收藏、关注走一波。