IT俱乐部 JavaScript 基于Vue3+IntersectionObserver实现高性能图片懒加载

基于Vue3+IntersectionObserver实现高性能图片懒加载

一、原理概述

图片懒加载的核心思想是:图片进入用户可视区域时才加载真实图片,未进入时显示占位图

Vue3 中实现懒加载最优雅的方式是使用 IntersectionObserver API,相比传统的 scroll 事件监听,它具备以下优势:

  • 性能更好:浏览器自动优化交叉观察,无需手动计算位置
  • 更省资源:元素离开视口后自动暂停监听
  • 代码更简洁:几行配置即可完成复杂的懒加载逻辑

懒加载实现流程:

  1. 页面初始时,图片 src 使用占位图,真实地址存在 data-src 属性中
  2. 创建 IntersectionObserver 实例,监听所有图片元素
  3. 当图片进入视口(露出比例超过阈值)时,将 data-src 的值赋给 src
  4. 图片加载完成后取消观察,释放资源

二、核心代码实现

配置项定义

/** 图片总数 */
const TOTAL_ITEMS = 99

/** 默认占位图 - 页面初始时显示的轻量图片 */
const DEFAULT_IMG = 'https://pica.zhimg.com/v2-f052aa50ca65df4bad1c3b7e4084d00e_1440w.jpg'

/** 真实图片地址模板 - 接收索引参数,生成不同的随机图片 URL */
const IMG_URL_TEMPLATE = (index: number) => `https://picsum.photos/400/600?r=${index}`

DOM 引用获取

/**
 * 获取所有需要懒加载的图片 DOM 引用
 * 在 v-for 中使用 ref,Vue 会自动把所有 DOM 存入一个数组里
 * ref 表示引用数组类型
 */
const imgRefs = ref([])

懒加载核心逻辑

/** IntersectionObserver 实例引用,组件销毁时需要手动清理 */
let observer: IntersectionObserver | null = null

/**
 * 初始化懒加载监听
 * 使用 async 是为了确保 DOM 渲染完成后再执行监听
 */
async function initLazyLoad() {
  // 创建观察者实例,传入回调函数和配置项
  observer = new IntersectionObserver(
    // entries: 触发回调时,传入所有发生交叉变化的元素数组
    // observer: 观察者实例本身,用于调用 unobserve 取消观察
    (entries, observer) => {
      // 遍历所有发生变化的元素
      for (const entry of entries) {
        // isIntersecting: 元素是否进入视口
        // ! 为 false 时表示元素离开了视口,无需处理,直接跳过
        if (!entry.isIntersecting) continue

        // 将 entry.target 断言为 HTMLImageElement 类型
        // 因为 ref 数组中存储的正是图片 DOM 元素
        const img = entry.target as HTMLImageElement

        // dataset: 获取元素上 data-* 自定义属性
        // data-src="真实图片地址" 存储在 dataset.src 中
        const realSrc = img.dataset.src

        // 将真实图片地址赋值给 src,触发浏览器加载真实图片
        if (realSrc) img.src = realSrc

        // 加载完成后立即取消观察该图片
        // 避免已加载的图片占用观察者资源,提升性能
        observer.unobserve(img)
      }
    },
    {
      // threshold: 交叉比例阈值,0.01 表示图片露出 1% 就触发回调
      // 值范围 0~1,值越小越早触发,但可能浪费带宽
      threshold: 0.01,
    },
  )

  // 等待 DOM 渲染完成后再开始监听
  // nextTick 确保 v-for 循环的图片 DOM 已经渲染到页面
  await nextTick()

  // 遍历所有图片 DOM,逐个注册到观察者中
  // observe 之后,观察者就会开始监听该元素的可见性变化
  imgRefs.value.forEach((img) => observer?.observe(img))
}

资源清理(防止内存泄漏)

/**
 * 销毁观察者实例
 * ⚠️ 组件销毁时必须调用!否则会内存泄漏
 */
function destroyLazyLoad() {
  // 未初始化则直接返回,避免报错
  if (!observer) return

  // 遍历所有图片,先取消对每个图片的观察
  // disconnect 之前建议先调用 unobserve,避免遗留监听
  imgRefs.value.forEach((img) => observer!.unobserve(img))

  // disconnect: 完全销毁观察者,释放所有资源
  observer.disconnect()

  // 重置为 null,标记已清理
  observer = null
}

生命周期钩子绑定

/** 组件挂载到页面后,立即初始化懒加载监听 */
onMounted(() => {
  initLazyLoad()
})

/**
 * 组件销毁前,清理观察者实例
 * 防止用户切换页面后,观察者仍在后台运行消耗资源
 */
onUnmounted(() => {
  destroyLazyLoad()
})

三、完整代码示例

🔥 图片懒加载功能 | 核心优势:进入视口才加载图片 → 首屏加载速度提升 80%、节省带宽资源、避免页面卡顿,大幅优化多图场景用户体验
image
/** 图片总数 - 控制列表中显示的图片数量 */ const TOTAL_ITEMS = 99 /** 默认占位图 - 未加载前显示的轻量图片 */ const DEFAULT_IMG = 'https://pica.zhimg.com/v2-f052aa50ca65df4bad1c3b7e4084d00e_1440w.jpg' /** 真实图片地址生成函数 - 接收索引,返回唯一随机图片 URL */ const IMG_URL_TEMPLATE = (index: number) => `https://picsum.photos/400/600?r=${index}` /** * DOM 引用数组 - 用于存储所有需要懒加载的图片 DOM * Vue 会自动将 v-for 中的 ref 收集到这个数组 */ const imgRefs = ref([]) /** 观察者实例 - 全局保存,组件销毁时需要手动清理 */ let observer: IntersectionObserver | null = null /** * 初始化懒加载核心逻辑 * 1. 创建 IntersectionObserver 实例 * 2. 等待 DOM 渲染完成后开始监听 */ async function initLazyLoad() { // 创建观察者,配置交叉阈值为 1% observer = new IntersectionObserver( (entries, observer) => { // entries: 当前帧内所有发生交叉变化的元素列表 for (const entry of entries) { // 只处理「进入视口」的元素,「离开视口」时跳过 if (!entry.isIntersecting) continue // 获取触发回调的图片 DOM 元素 const img = entry.target as HTMLImageElement // 从 data-src 属性读取真实图片地址 const realSrc = img.dataset.src // 将真实地址赋值给 src,触发图片加载 if (realSrc) img.src = realSrc // ⚠️ 关键:加载完成后立即取消观察 // 避免已加载图片继续占用观察者资源 observer.unobserve(img) } }, { // threshold: 触发加载的可见比例 // 0.01 = 图片露出 1% 时就触发,适合需要提前加载的场景 threshold: 0.01, }, ) // 等待 Vue 更新 DOM 后再执行监听 // 确保 v-for 循环的 img 元素已经渲染到页面 await nextTick() // 将所有图片 DOM 注册到观察者,开始监听 imgRefs.value.forEach((img) => observer?.observe(img)) } /** * 销毁观察者,释放资源 * ⚠️ 必须在组件销毁时调用,防止内存泄漏 */ function destroyLazyLoad() { if (!observer) return // 先取消所有图片的观察 imgRefs.value.forEach((img) => observer!.unobserve(img)) // 完全销毁观察者实例 observer.disconnect() // 重置为 null observer = null } /** 组件挂载时启动懒加载 */ onMounted(() => { initLazyLoad() }) /** 组件销毁前清理资源 */ onUnmounted(() => { destroyLazyLoad() }) .app-content { /* CSS 变量:统一样式配置,方便维护 */ --item-gap: 16px; /* 网格项之间的间距 */ --item-min-width: 150px; /* 网格项的最小宽度,响应式适配 */ --item-height: 300px; /* 图片卡片固定高度 */ } /* 功能描述样式 - 左侧蓝色边框提示框 */ .lazy-desc { margin-bottom: 16px; padding: 8px 16px; background: #f0f9ff; /* 浅蓝色背景 */ border-left: 4px solid #409eff; /* 左侧蓝色强调条 */ border-radius: 4px; color: #1f2937; font-size: 14px; font-weight: 500; line-height: 1.5; } /* 响应式网格布局 - 自动填充,最小宽度 150px */ .card-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--item-min-width), 1fr)); gap: var(--item-gap); } .card-list .item { cursor: pointer; height: var(--item-height); border-radius: 4px; box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35); /* 卡片阴影 */ overflow: hidden; /* 隐藏图片放大时超出边框的部分 */ } .card-list .item:hover img { transform: scale(1.5); /* 鼠标悬停时图片放大 1.5 倍 */ } .card-list .item img { display: block; width: 100%; height: 100%; transition: all 0.32s; /* 过渡动画,使缩放更平滑 */ }

四、核心总结

本文通过 Vue3 + IntersectionObserver 实现了高性能图片懒加载方案,核心要点:

要点 说明
IntersectionObserver 替代 scroll 事件,浏览器自动优化,性能更优
占位图 + data-src 初始显示占位图,真实地址存在 data-src 中
observer.unobserve() 加载完成后取消监听,避免资源浪费
onUnmounted 清理 组件销毁时调用 disconnect(),防止内存泄漏

该方案在多图列表场景下效果显著,可直接应用于商品列表、朋友圈图片流、相册等业务场景。

以上就是基于Vue3+IntersectionObserver实现高性能图片懒加载的详细内容,更多关于Vue3 IntersectionObserver图片懒加载的资料请关注IT俱乐部其它相关文章!

本文收集自网络,不代表IT俱乐部立场,转载请注明出处。https://www.2it.club/navsub/js/17783.html
上一篇
下一篇
联系我们

联系我们

在线咨询: QQ交谈

邮箱: 1120393934@qq.com

工作时间:周一至周五,9:00-17:30,节假日休息

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部