uni-app 里的图片懒加载怎么做?原理、方案和常见坑
本文默认语境是 uni-app x + Vue 3,重点讨论 H5 与 微信小程序端。如果面试官追问 App 端,要主动补一句:App 端底层渲染实现不同,细节表现可能和 H5 / 小程序不完全一致,但核心目标仍然是“延后非首屏图片请求,降低首屏压力”。
面试速答(30 秒版 TL;DR)
- 图片懒加载的本质不是“图片晚一点显示”,而是 非首屏图片不立刻发请求,等图片快进入可视区域时再加载。
uni-app里做图片懒加载,不能只背lazy-load这个属性,更要分平台说:- 微信小程序端:
image组件支持lazy-load - H5:更常见是浏览器原生
loading="lazy"、IntersectionObserver,或列表虚拟化
- 微信小程序端:
- 真正的优化收益主要有 3 个:减少首屏请求数、降低带宽占用、缩短首屏渲染压力。
- 但懒加载不是万能药,面试里一定要补充边界:首屏关键图不要懒、要预留尺寸避免 CLS、长列表只懒加载还不够时要上虚拟列表。
心智模型:懒加载优化的是“请求时机”,不是“图片解码算法”
很多人一提图片优化,容易把几件事混在一起:
- 图片压缩
- 图片格式选择
- CDN 裁剪
- 图片懒加载
这几件事解决的是不同问题。
图片懒加载最核心解决的是:
- 当前屏幕看不到的图片,为什么要在首屏就发请求?
所以它优化的是:
- 请求发起时机
- 首屏资源竞争关系
而不是直接改变:
- 图片文件大小
- 图片编码方式
可以把它理解成:
- 图片压缩是在“把货变轻”
- 图片懒加载是在“让非急件晚点发车”
先记主流程:是否接近可视区,决定了图片是否现在发请求。
面试时按这条线讲,通常就不会把“图片压缩”和“懒加载时机控制”混为一谈。
1. 为什么 uni-app 项目经常需要图片懒加载
uni-app 很多业务页面都容易踩到这类场景:
- 商品列表
- 瀑布流
- 文章流
- 评论区晒单图
- 营销活动页长图文
这些页面的共同问题是:
- 图片多
- 首屏真正可见的图片有限
- 用户未必会滚到页面底部
如果不做懒加载,常见后果是:
- 首屏时并发图片请求过多
- 弱网环境白屏或骨架屏时间变长
- 和首屏核心接口、JS、CSS 抢网络带宽
- 内存占用增大,低端机更容易卡顿
所以面试里可以先下结论:
- 图片懒加载特别适合“图片数量多、首屏可见比例低”的列表型页面。
2. uni-app 常见实现方案要按平台讲
2.1 微信小程序端:优先了解 image 的 lazy-load
在微信小程序端,image 组件支持 lazy-load,这通常是最先要答到的点。
示例:
<template>
<view class="goods-list">
<image v-for="item in list" :key="item.id" class="goods-img" :src="item.cover" mode="aspectFill" lazy-load />
</view>
</template>
你可以这样解释它:
lazy-load会让图片在接近可视区域前不立即加载- 它是平台提供的能力,开发成本低
- 但它只解决“图片请求别太早”,不解决“列表节点太多”
也就是说:
- 它能减轻网络压力,但不能代替虚拟列表。
2.2 H5:常见是原生 loading="lazy" 或 IntersectionObserver
在 H5 端,懒加载更常见的做法是两类:
- 浏览器原生
loading="lazy" - 用
IntersectionObserver监听进入视口后再替换真实src
如果是自己封装图片组件,IntersectionObserver 更稳,因为可控性更强。
示例思路:
<template>
<img ref="imgRef" :src="loaded ? realSrc : placeholder" :alt="alt" width="320" height="180" />
</template>
<script setup lang="ts">
import { onMounted, onBeforeUnmount, ref } from 'vue'
const props = defineProps<{
realSrc: string
placeholder: string
alt?: string
}>()
const imgRef = ref<HTMLImageElement | null>(null)
const loaded = ref(false)
let observer: IntersectionObserver | null = null
onMounted(() => {
if (!imgRef.value) return
observer = new IntersectionObserver(
(entries) => {
const entry = entries[0]
if (!entry?.isIntersecting) return
loaded.value = true
observer?.disconnect()
observer = null
},
{
rootMargin: '120px',
},
)
observer.observe(imgRef.value)
})
onBeforeUnmount(() => {
observer?.disconnect()
observer = null
})
</script>
这里最值得讲给面试官听的是:
rootMargin可以提前预加载,避免用户滚到眼前才开始请求- 占位图和宽高要提前给,避免布局抖动
- 组件销毁时要断开 observer,避免泄漏
2.3 scroll-view、长列表、瀑布流要额外小心
这类场景有两个高频坑:
- 可视区域不是整个页面,而是某个滚动容器
- 图片很多,DOM 节点本身就可能成为瓶颈
所以这时你要补一句:
- 如果是在
scroll-view里做懒加载,要确认监听的根节点是不是滚动容器 - 如果列表量级很大,应该把 懒加载 + 虚拟列表 / 分页加载 组合起来
3. 面试真正想听的:图片懒加载到底带来什么收益
你可以从 3 层来说。
3.1 网络层
- 减少首屏时同时发出的图片请求数
- 让关键接口、核心脚本先拿到带宽
3.2 渲染层
- 减轻首屏图片解码和绘制压力
- 降低低端机首屏卡顿风险
3.3 业务层
- 用户没滑到的图片,可能根本不需要下载
- 适合信息流、电商、社区类页面
一句话总结就是:
- 懒加载优化的是“首屏资源分配效率”。
4. 工程上怎么设计一个更稳的懒加载方案
不要只说“给图片加个属性”。更完整的答法是:
4.1 先区分关键图和非关键图
首屏关键图通常不建议懒加载,例如:
- 首屏 Banner
- 商品首图
- 详情页主图
因为这些图本来就是首屏内容,懒加载只会把本该立刻展示的资源推迟。
4.2 必须预留尺寸
无论是 H5 还是小程序,图片没加载前都应尽量预留宽高或占位区域。
否则会导致:
- 页面跳动
- 用户误触
- 累积布局偏移(CLS)变差
4.3 提供失败兜底
图片加载失败后至少要有:
- 默认占位图
- 重试逻辑或弱提示
尤其在小程序场景下,CDN、鉴权链接、临时 URL 失效都很常见。
4.4 合理设置预加载距离
太近:
- 用户已经看到空白,体验差
太远:
- 失去懒加载意义
一般会在“提前一屏左右”这个量级做调优,但最终要按:
- 图片大小
- 网络环境
- 列表滚动速度
来决定。
5. 典型题 & 标准答法
Q1:uni-app 里怎么做图片懒加载?
标准答法:
- 先按平台回答。微信小程序端可用
image组件的lazy-load;H5 端通常用浏览器原生loading="lazy"或IntersectionObserver封装懒加载图片组件。 - 工程上不能只看“能不能晚加载”,还要处理占位、宽高预留、失败兜底,以及长列表下和虚拟列表的组合。
Q2:图片懒加载和图片压缩有什么区别?
- 图片压缩优化的是单个文件体积
- 图片懒加载优化的是请求发起时机
- 两者通常应该一起做,而不是二选一
Q3:为什么图片懒加载有时效果不明显?
常见原因:
- 页面本来图片就不多
- 首屏图也被错误懒加载,反而拖慢了体验
- 真正瓶颈在接口瀑布流、长列表渲染或大图体积,而不是请求时机
6. 常见追问
6.1 懒加载能不能解决长列表卡顿?
不能完全解决。
因为长列表卡顿常常来自:
- DOM 节点太多
- 数据绑定太多
- 图片解码只是其中一部分
更完整的方案应该是:
- 懒加载
- 分页
- 虚拟列表
- 图片压缩
6.2 为什么有些图片已经懒加载了,滚动还是卡?
因为“加载晚一点”和“渲染轻一点”不是同一件事。
可能的根因包括:
- 图片尺寸过大,解码重
- 同屏节点太多
- 每次滚动触发太多响应式更新
- 使用了不合适的瀑布流实现
6.3 小程序端是不是加了 lazy-load 就万事大吉?
不是。
你还要看:
- 页面是不是超长列表
- 图片是否设置了合理尺寸和裁剪模式
- 是否有骨架屏或占位
- CDN 图是否做了缩略图裁剪
7. 易错点 / 坑
- 把首屏主视觉图片也做懒加载,导致首屏反而变慢
- 不预留宽高,图片加载后页面跳动
- 列表非常大,却只做懒加载,不做分页或虚拟列表
- 用滚动事件手写监听却没有节流,造成额外性能开销
- 只在 H5 验证通过,没有检查微信小程序端的真实表现
速记要点
- 图片懒加载 = 延后非首屏图片请求
- 小程序端常答
image lazy-load - H5 常答
loading="lazy"或IntersectionObserver - 懒加载解决的是请求时机,不是文件体积
- 关键图不懒,非关键图才懒
- 要配合宽高占位、失败兜底、虚拟列表一起讲