scroll-view 中 lazy-load 不起作用是因为微信小程序规范限制其仅对页面级滚动生效,scroll-view 为自定义滚动容器,不触发原生滚动路径;官方推荐使用 uni.createIntersectionObserver 手动监听并配合占位、压缩、防抖实现真正有效的懒加载。

scroll-view里lazy-load为什么不起作用
因为微信小程序规范明确限制:lazy-load只对页面级滚动(onPageScroll)生效,scroll-view是自定义滚动容器,不走原生滚动路径。H5 和部分 App 端可能“看起来”能用,但行为不可靠——尤其在安卓 WebView 或 iOS 微信中,图片常一直空白、@load不触发、控制台也无报错。
错误写法:<scroll-view scroll-y="true"><image :src="url" lazy-load></image></scroll-view>
-
scroll-view必须显式设scroll-y="true"且父容器有固定高度(如height: 600rpx),否则滚动区域根本不存在 - 哪怕写了
lazy-load="true",在scroll-view内它就是静默失效,不是 bug,是平台设计如此 - 不要指望它在瀑布流、嵌套滚动或 transform 平移容器里工作
必须用uni.createIntersectionObserver手动监听
这是 uni-app 官方推荐、多端兼容的方案,比 bindscroll 更准、更轻量,不受 iOS scrollTop 不准或小程序节流丢事件影响。
- 观察器必须在
onReady或mounted后创建,不能写在data或created里——此时this.$refs还为空 - 目标必须是真实 DOM 节点:
observer.observe(this.$refs.imgRef),不能传字符串选择器(如'#img-1') - 动态列表要等
this.$nextTick()后再调用observe(),否则节点未挂载,监听失败 - 每次
observe()后记得observer.disconnect(),否则内存泄漏 - 小程序端
threshold只接受数组(如[0, 0.1, 0.5]),设成数字(如0.2)会静默失败;H5 端才支持单数值
图片没占位,滚动就跳、加载就卡
布局抖动(layout thrashing)和重复加载的根本原因,是图片加载前高度为 0,导致 DOM 渲染后反复重排。uni-app 没有自动 aspect-ratio 回退机制。
- 最优解:服务端返回图片宽高字段,前端直接设
:style="{ height: item.height + 'px' }" - 做不到就用
uni.getImageInfo预加载——它比new Image()可靠得多(后者在安卓 WebView 里常不触发解码) - 所有
<image>必须设固定宽高或mode(如mode="widthFix"),否则无法占位 - 用
:style="{ opacity: loaded ? 1 : 0 }"+ CSStransition替代v-if,避免组件销毁重建、src重赋值 - 加占位样式:
.img-placeholder { background-color: #f5f5f5; transition: opacity 0.3s; },防止白屏闪动
原图不压缩,懒加载只是把卡顿延迟到滑动那一刻
如果图片是 5MB 原图,哪怕延迟加载,用户一滑到就解码卡死。懒加载的前提是图片本身足够小。
- CDN 动态裁剪优先:用阿里云 OSS、腾讯云 COS 等,URL 加参数如
?x-oss-process=image/resize,w_200/format,webp,把原图压到几 KB - 本地图片或非 CDN 图,统一走工具函数处理:
resizeImg(url, 200),避免硬编码 URL 参数 - 别让 Vue 渲染所有项——长列表要配合分页或虚拟滚动(uni-app 无内置,需手算
scrollTop+offsetTop或引入第三方库) -
key一定用唯一 ID,别用index,否则数据更新时渲染错乱、图片重复加载
真正有效的滚动加载,从来不是“开了个属性就完事”,而是占位、压缩、监听、防抖四者缺一不可。最容易被忽略的是:服务端没给宽高,前端又没预加载,结果所有图片都塌缩成一条线,一滑就跳,一跳就重排,一重排就触发新一轮懒加载——死循环就这么来了。


















