查看: 2504|回复: 3

Vue3图片懒加载Hooks封装与vue3-lazyload实践

[复制链接]
发表于 前天 15:00 | 显示全部楼层 |阅读模式
在现代 Vue3 前端开发中,页面图片较多时,如果初始化阶段就加载全部图片,会拉长首屏时间并增加移动端流量消耗。图片懒加载的思路是:图片进入浏览器可视区域后,再把真实地址写入 src,从而减少初始请求量,让 HTML、CSS 等关键资源优先加载。本文围绕 Vue3 Hooks 封装,给出原生 IntersectionObserver 与 vue3-lazyload 库两种实现,并说明关键参数、调用逻辑和清理注意事项。

一、懒加载原理与目录准备
懒加载的本质不是一开始就请求所有图片,而是等图片进入可视区域再加载。实现时通常把真实图片地址放在 data-src,等条件满足后再赋值给 img.src。Vue3 中可以使用 IntersectionObserver 监听元素是否进入可视区。项目准备方面,在 src 下创建 hooks 文件夹,并新建 useLazyLoad.js。若要使用库方案,先安装 vue3-lazyload:
  1. npm install vue3-lazyload
复制代码

二、原生 Hooks 封装
原生方案不引入第三方库,直接利用 vue 的 ref、onMounted、onBeforeUnmount、nextTick 和浏览器 IntersectionObserver。完整示例:
  1. import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
  2. export default function useLazyLoad() {
  3. // 用于存储图片是否加载完成的响应式状态
  4. const isLoaded = ref(false);
  5. // 加载图片的函数
  6. const loadImage = (imgElement) => {
  7. if (!imgElement) return;
  8. const observer = new IntersectionObserver((entries) => {
  9. entries.forEach((entry) => {
  10. if (entry.isIntersecting) {
  11. const img = entry.target;
  12. img.src = img.dataset.src;
  13. isLoaded.value = true;
  14. observer.disconnect();
  15. }
  16. });
  17. });
  18. observer.observe(imgElement);
  19. };
  20. // 在组件挂载时执行的逻辑
  21. onMounted(() => {
  22. // 使用nextTick确保DOM更新完成后再获取图片元素
  23. nextTick(() => {
  24. const imgElements = document.querySelectorAll('img[data-src]');
  25. imgElements.forEach((img) => {
  26. loadImage(img);
  27. });
  28. });
  29. });
  30. // 在组件卸载前执行的逻辑
  31. onBeforeUnmount(() => {
  32. const observers = [];
  33. const imgElements = document.querySelectorAll('img[data-src]');
  34. imgElements.forEach((img) => {
  35. const observer = img.__observer;
  36. if (observer) {
  37. observers.push(observer);
  38. }
  39. });
  40. observers.forEach((observer) => {
  41. observer.disconnect();
  42. });
  43. });
  44. return {
  45. isLoaded
  46. };
  47. }
复制代码
代码逻辑说明:isLoaded 是一个响应式引用,用来跟踪图片是否已经加载完成。loadImage 接收图片元素,创建 IntersectionObserver 实例;当 entries 中的 entry.isIntersecting 为 true 时,说明图片进入可视区域,此时把 img.src 设置为 img.dataset.src,也就是 data-src 中保存的真实地址,并将 isLoaded 置为 true,然后调用 observer.disconnect() 断开该观察器。onMounted 在组件挂载时执行,内部用 nextTick 等待 DOM 更新完成,再通过 document.querySelectorAll('img[data-src]') 获取所有待懒加载图片,并逐个调用 loadImage。onBeforeUnmount 在组件卸载前执行,原示例尝试从 img.__observer 读取观察器并统一 disconnect,以避免内存泄漏。这里要注意:如果 loadImage 中没有把创建的 observer 保存到 img.__observer,那么卸载阶段的收集逻辑无法拿到实例;按该清理思路实现时,应在 observe 之后补上 img.__observer = observer 或在闭包中维护观察器集合。

三、使用 vue3-lazyload 库封装
库方案先执行 npm install vue3-lazyload,然后在 Hooks 中导入 lazyLoad。示例:
  1. import { ref, onMounted, onBeforeUnmount } from 'vue';
  2. import lazyLoad from 'vue3-lazyload';
  3. export default function useLazyLoad() {
  4. // 创建一个响应式的对象来存储图片加载状态
  5. const isLoaded = ref(false);
  6. // 定义加载图片的函数
  7. const loadImage = (imgElement) => {
  8. if (!imgElement) return;
  9. const observer = new IntersectionObserver((entries) => {
  10. entries.forEach((entry) => {
  11. if (entry.isIntersecting) {
  12. lazyLoad(entry.target);
  13. isLoaded.value = true;
  14. observer.disconnect();
  15. }
  16. });
  17. });
  18. observer.observe(imgElement);
  19. };
  20. // 在组件挂载时,绑定加载图片的事件
  21. onMounted(() => {
  22. const imgElements = document.querySelectorAll('img[data-src]');
  23. imgElements.forEach((img) => {
  24. loadImage(img);
  25. });
  26. });
  27. // 在组件卸载前,清除所有的观察器
  28. onBeforeUnmount(() => {
  29. const observers = [];
  30. const imgElements = document.querySelectorAll('img[data-src]');
  31. imgElements.forEach((img) => {
  32. const observer = img.__observer;
  33. if (observer) {
  34. observers.push(observer);
  35. }
  36. });
  37. observers.forEach((observer) => {
  38. observer.disconnect();
  39. });
  40. });
  41. return {
  42. isLoaded
  43. };
  44. }
复制代码
这版逻辑与原生方案相近:isLoaded 记录加载状态;loadImage 创建 IntersectionObserver,当 entry.isIntersecting 为 true 时调用 lazyLoad(entry.target),把目标图片交给 vue3-lazyload 处理,同时把 isLoaded 置为 true,并断开观察器。onMounted 中查询 img[data-src] 并逐个进入观察。onBeforeUnmount 中同样按 img.__observer 收集观察器并 disconnect,这里同样需要确保观察器实例被保存到元素上,否则清理逻辑不会按预期工作。

四、性能优化与验证
实现懒加载后,可以从几个方面优化:合理设置 IntersectionObserver 的阈值,避免过于频繁地检查图片是否进入可视区;对图片进行适当压缩,减少单张图片的数据量;结合 Chrome 开发者工具的性能面板评估懒加载效果,观察首屏请求和加载时机的变化。对于移动端或图片较多的页面,按需加载能减少不必要流量,并改善关键资源加载顺序。

五、总结
Vue3 图片懒加载可以通过自定义 Hooks 统一封装。原生方案依赖 IntersectionObserver 和 data-src,适合希望减少依赖的项目;vue3-lazyload 方案通过安装库并调用 lazyLoad 完成加载。两种方式都要关注观察器的创建、保存与断开,尤其是组件卸载前的清理,否则容易留下内存泄漏隐患。
回复

使用道具 举报

发表于 前天 19:00 | 显示全部楼层

Re: Vue3图片懒加载Hooks封装与vue3-lazyload实践

感谢分享,整体把懒加载的原理、原生 IntersectionObserver 封装和 vue3-lazyload 的引入都串起来了,对准备做图片优化的项目挺有参考价值。原生 Hooks 里那个清理细节很关键:如果 loadImage 中只创建并 observe,没有把 observer 存到 img.__observer,onBeforeUnmount 里再读 img.__observer 就会拿不到实例,disconnect 也就无效;按文中思路,确实应该在 observe 后补上保存,或者统一维护观察器集合。另一个小疑问是,onMounted 里用 querySelectorAll 一次性取所有 img[data-src],如果图片列表是异步渲染或分页追加的,后续新图片可能不会被观察到,这方面不知道有没有推荐处理方式。第三部分 vue3-lazyload 到最后停在 onBeforeUnmount 的清理逻辑,看起来还没贴完,方便的话可以补全库方案的完整调用和卸载清理,这样对比会更清楚。
回复 支持 反对

使用道具 举报

发表于 前天 19:10 | 显示全部楼层

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[data-src]') 会查整个文档,最好限定在组件根节点范围内,避免影响其他组件。 库方案那里,如果已经用了 vue3-lazyload,很多时候直接 v-lazy 指令会更省事,再手写一套 IntersectionObserver 会有点重复。 第三部分最后好像没贴完,onBeforeUnmount 后面断了,如果方便可以补上完整清理代码。
回复 支持 反对

使用道具 举报

发表于 前天 19:20 | 显示全部楼层

Re: Vue3图片懒加载Hooks封装与vue3-lazyload实践

写得挺系统的,原理、原生封装和库方案都覆盖到了。我这边看下来有两点小提醒:一是原生方案里 onBeforeUnmount 依赖 img.__observer 来收集观察器,但 loadImage 里创建 observer 后没有挂到 img 上,所以实际清理会拿不到实例,得按你说的补上 img.__observer = observer,或者在外面用 Set 存起来统一 disconnect。二是 isLoaded 是单个 ref,多张图片场景下只能表示“有图片加载完成了”,要精确控制每张图的占位状态可能不太够。库方案里如果已经用了 vue3-lazyload,再手动 new IntersectionObserver 会有点重复,不过如果是为了统一 Hooks 封装也能接受。整体思路值得参考,期待完整版。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-12 12:30 , Processed in 0.030650 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部