跳到主要内容

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 很多业务页面都容易踩到这类场景:

  • 商品列表
  • 瀑布流
  • 文章流
  • 评论区晒单图
  • 营销活动页长图文

这些页面的共同问题是:

  1. 图片多
  2. 首屏真正可见的图片有限
  3. 用户未必会滚到页面底部

如果不做懒加载,常见后果是:

  • 首屏时并发图片请求过多
  • 弱网环境白屏或骨架屏时间变长
  • 和首屏核心接口、JS、CSS 抢网络带宽
  • 内存占用增大,低端机更容易卡顿

所以面试里可以先下结论:

  • 图片懒加载特别适合“图片数量多、首屏可见比例低”的列表型页面。

2. uni-app 常见实现方案要按平台讲

2.1 微信小程序端:优先了解 imagelazy-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
  • 懒加载解决的是请求时机,不是文件体积
  • 关键图不懒,非关键图才懒
  • 要配合宽高占位、失败兜底、虚拟列表一起讲