Vue3图片懒加载Hooks封装与vue3-lazyload实践
在现代 Vue3 前端开发中,页面图片较多时,如果初始化阶段就加载全部图片,会拉长首屏时间并增加移动端流量消耗。图片懒加载的思路是:图片进入浏览器可视区域后,再把真实地址写入 src,从而减少初始请求量,让 HTML、CSS 等关键资源优先加载。本文围绕 Vue3 Hooks 封装,给出原生 IntersectionObserver 与 vue3-lazyload 库两种实现,并说明关键参数、调用逻辑和清理注意事项。一、懒加载原理与目录准备
懒加载的本质不是一开始就请求所有图片,而是等图片进入可视区域再加载。实现时通常把真实图片地址放在 data-src,等条件满足后再赋值给 img.src。Vue3 中可以使用 IntersectionObserver 监听元素是否进入可视区。项目准备方面,在 src 下创建 hooks 文件夹,并新建 useLazyLoad.js。若要使用库方案,先安装 vue3-lazyload:
npm install vue3-lazyload
二、原生 Hooks 封装
原生方案不引入第三方库,直接利用 vue 的 ref、onMounted、onBeforeUnmount、nextTick 和浏览器 IntersectionObserver。完整示例:
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
export default function useLazyLoad() {
// 用于存储图片是否加载完成的响应式状态
const isLoaded = ref(false);
// 加载图片的函数
const loadImage = (imgElement) => {
if (!imgElement) return;
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
isLoaded.value = true;
observer.disconnect();
}
});
});
observer.observe(imgElement);
};
// 在组件挂载时执行的逻辑
onMounted(() => {
// 使用nextTick确保DOM更新完成后再获取图片元素
nextTick(() => {
const imgElements = document.querySelectorAll('img');
imgElements.forEach((img) => {
loadImage(img);
});
});
});
// 在组件卸载前执行的逻辑
onBeforeUnmount(() => {
const observers = [];
const imgElements = document.querySelectorAll('img');
imgElements.forEach((img) => {
const observer = img.__observer;
if (observer) {
observers.push(observer);
}
});
observers.forEach((observer) => {
observer.disconnect();
});
});
return {
isLoaded
};
}
代码逻辑说明:isLoaded 是一个响应式引用,用来跟踪图片是否已经加载完成。loadImage 接收图片元素,创建 IntersectionObserver 实例;当 entries 中的 entry.isIntersecting 为 true 时,说明图片进入可视区域,此时把 img.src 设置为 img.dataset.src,也就是 data-src 中保存的真实地址,并将 isLoaded 置为 true,然后调用 observer.disconnect() 断开该观察器。onMounted 在组件挂载时执行,内部用 nextTick 等待 DOM 更新完成,再通过 document.querySelectorAll('img') 获取所有待懒加载图片,并逐个调用 loadImage。onBeforeUnmount 在组件卸载前执行,原示例尝试从 img.__observer 读取观察器并统一 disconnect,以避免内存泄漏。这里要注意:如果 loadImage 中没有把创建的 observer 保存到 img.__observer,那么卸载阶段的收集逻辑无法拿到实例;按该清理思路实现时,应在 observe 之后补上 img.__observer = observer 或在闭包中维护观察器集合。
三、使用 vue3-lazyload 库封装
库方案先执行 npm install vue3-lazyload,然后在 Hooks 中导入 lazyLoad。示例:
import { ref, onMounted, onBeforeUnmount } from 'vue';
import lazyLoad from 'vue3-lazyload';
export default function useLazyLoad() {
// 创建一个响应式的对象来存储图片加载状态
const isLoaded = ref(false);
// 定义加载图片的函数
const loadImage = (imgElement) => {
if (!imgElement) return;
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
lazyLoad(entry.target);
isLoaded.value = true;
observer.disconnect();
}
});
});
observer.observe(imgElement);
};
// 在组件挂载时,绑定加载图片的事件
onMounted(() => {
const imgElements = document.querySelectorAll('img');
imgElements.forEach((img) => {
loadImage(img);
});
});
// 在组件卸载前,清除所有的观察器
onBeforeUnmount(() => {
const observers = [];
const imgElements = document.querySelectorAll('img');
imgElements.forEach((img) => {
const observer = img.__observer;
if (observer) {
observers.push(observer);
}
});
observers.forEach((observer) => {
observer.disconnect();
});
});
return {
isLoaded
};
}
这版逻辑与原生方案相近:isLoaded 记录加载状态;loadImage 创建 IntersectionObserver,当 entry.isIntersecting 为 true 时调用 lazyLoad(entry.target),把目标图片交给 vue3-lazyload 处理,同时把 isLoaded 置为 true,并断开观察器。onMounted 中查询 img 并逐个进入观察。onBeforeUnmount 中同样按 img.__observer 收集观察器并 disconnect,这里同样需要确保观察器实例被保存到元素上,否则清理逻辑不会按预期工作。
四、性能优化与验证
实现懒加载后,可以从几个方面优化:合理设置 IntersectionObserver 的阈值,避免过于频繁地检查图片是否进入可视区;对图片进行适当压缩,减少单张图片的数据量;结合 Chrome 开发者工具的性能面板评估懒加载效果,观察首屏请求和加载时机的变化。对于移动端或图片较多的页面,按需加载能减少不必要流量,并改善关键资源加载顺序。
五、总结
Vue3 图片懒加载可以通过自定义 Hooks 统一封装。原生方案依赖 IntersectionObserver 和 data-src,适合希望减少依赖的项目;vue3-lazyload 方案通过安装库并调用 lazyLoad 完成加载。两种方式都要关注观察器的创建、保存与断开,尤其是组件卸载前的清理,否则容易留下内存泄漏隐患。
Re: Vue3图片懒加载Hooks封装与vue3-lazyload实践
感谢分享,整体把懒加载的原理、原生 IntersectionObserver 封装和 vue3-lazyload 的引入都串起来了,对准备做图片优化的项目挺有参考价值。原生 Hooks 里那个清理细节很关键:如果 loadImage 中只创建并 observe,没有把 observer 存到 img.__observer,onBeforeUnmount 里再读 img.__observer 就会拿不到实例,disconnect 也就无效;按文中思路,确实应该在 observe 后补上保存,或者统一维护观察器集合。另一个小疑问是,onMounted 里用 querySelectorAll 一次性取所有 img,如果图片列表是异步渲染或分页追加的,后续新图片可能不会被观察到,这方面不知道有没有推荐处理方式。第三部分 vue3-lazyload 到最后停在 onBeforeUnmount 的清理逻辑,看起来还没贴完,方便的话可以补全库方案的完整调用和卸载清理,这样对比会更清楚。Re: Vue3图片懒加载Hooks封装与vue3-lazyload实践
这篇整理思路挺清楚的,把原生 IntersectionObserver 和 vue3-lazyload 两种方案放在一起对比,实际项目里确实容易按图片数量和维护成本来选。 不过有个细节想提醒下:onBeforeUnmount 里是通过 img.__observer 来收集观察器的,但前面 loadImage 里创建 observer 后只调用了 observe,没有把 observer 挂到 img 上,所以卸载时大概率拿不到实例,清理会失效。可以改成 observe 之后补一句 img.__observer = observer,或者用 WeakMap 统一存 observer,卸载时再统一 disconnect。 另外 isLoaded 是单个 ref,多张图场景下一张加载完就会变成 true,如果要在模板里分别控制每张图的加载状态,可能得用 Map 或者按图片元素维护。还有 document.querySelectorAll('img') 会查整个文档,最好限定在组件根节点范围内,避免影响其他组件。 库方案那里,如果已经用了 vue3-lazyload,很多时候直接 v-lazy 指令会更省事,再手写一套 IntersectionObserver 会有点重复。 第三部分最后好像没贴完,onBeforeUnmount 后面断了,如果方便可以补上完整清理代码。Re: Vue3图片懒加载Hooks封装与vue3-lazyload实践
写得挺系统的,原理、原生封装和库方案都覆盖到了。我这边看下来有两点小提醒:一是原生方案里 onBeforeUnmount 依赖 img.__observer 来收集观察器,但 loadImage 里创建 observer 后没有挂到 img 上,所以实际清理会拿不到实例,得按你说的补上 img.__observer = observer,或者在外面用 Set 存起来统一 disconnect。二是 isLoaded 是单个 ref,多张图片场景下只能表示“有图片加载完成了”,要精确控制每张图的占位状态可能不太够。库方案里如果已经用了 vue3-lazyload,再手动 new IntersectionObserver 会有点重复,不过如果是为了统一 Hooks 封装也能接受。整体思路值得参考,期待完整版。
页:
[1]