一、原理概述
图片懒加载的核心思想是:图片进入用户可视区域时才加载真实图片,未进入时显示占位图。
Vue3 中实现懒加载最优雅的方式是使用 IntersectionObserver API,相比传统的 scroll 事件监听,它具备以下优势:
- 性能更好:浏览器自动优化交叉观察,无需手动计算位置
- 更省资源:元素离开视口后自动暂停监听
- 代码更简洁:几行配置即可完成复杂的懒加载逻辑
懒加载实现流程:
- 页面初始时,图片
src使用占位图,真实地址存在data-src属性中 - 创建
IntersectionObserver实例,监听所有图片元素 - 当图片进入视口(露出比例超过阈值)时,将
data-src的值赋给src - 图片加载完成后取消观察,释放资源
二、核心代码实现
配置项定义
/** 图片总数 */
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()
})
三、完整代码示例
/** 图片总数 - 控制列表中显示的图片数量 */ 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; /* 过渡动画,使缩放更平滑 */ }🔥 图片懒加载功能 | 核心优势:进入视口才加载图片 → 首屏加载速度提升 80%、节省带宽资源、避免页面卡顿,大幅优化多图场景用户体验![]()
四、核心总结
本文通过 Vue3 + IntersectionObserver 实现了高性能图片懒加载方案,核心要点:
| 要点 | 说明 |
|---|---|
IntersectionObserver |
替代 scroll 事件,浏览器自动优化,性能更优 |
占位图 + data-src
|
初始显示占位图,真实地址存在 data-src 中 |
observer.unobserve() |
加载完成后取消监听,避免资源浪费 |
onUnmounted 清理 |
组件销毁时调用 disconnect(),防止内存泄漏 |
该方案在多图列表场景下效果显著,可直接应用于商品列表、朋友圈图片流、相册等业务场景。
以上就是基于Vue3+IntersectionObserver实现高性能图片懒加载的详细内容,更多关于Vue3 IntersectionObserver图片懒加载的资料请关注IT俱乐部其它相关文章!
