因为列表页的图片太多,导致性能降低,所以决定用懒加载优化一下。所谓懒加载,就是图片没有显示的时候用占位图,等到显示的时候,将展位图改成真正要展示的图片。
一、onPageScroll(监听页面滚动)
提到懒加载,第一个想到的当然是onPageScroll啦。
首先,选一张占位图,并且在data中声明一下图片的高度和一个跟列表长度一样的false数组(这边为什么全部是false?因为要让图片全部先用占位图占着,等到显示的时候改为true)。
data: {
damoHeight :'110',
arry:[false,false,false,false,false,false]
},
然后在onload中处理首屏要转为true的图片,并进行渲染。
onLoad: function () {
let self = this;
swan.getSystemInfo({
success: res => {
let windowHeight = res.windowHeight;//获取屏幕的高度
}
});
//num是首屏要渲染的数量
let num = Math.ceil(windowHeight / self.data.damoHeight);// self.data.damoHeight为data中图片的高度
self.data.num = num;
for(let i = 0; i < num; i++){
self.data.arry[i] = true;//将要渲染的几张图片在数组里改为true
};
//将得到的新数组渲染到页面
self.setData({
arry:self.data.arry
});
}
接着在onPageScroll方法中监听scrollTop(滚动顶端的值即滚动的像素),计算有几张图片的状态要改为true,从而实现想要的效果。
onPageScroll: function (res){
let self = this;
let str = parseInt(res.scrollTop / self.data.damoHeight);// self.data.damoHeight为data中图片的高度
that.data.arry[self.data.num+str] = true;//这边的序号要加上之前已经显示的数量
//将得到的新数组渲染到页面
that.setData({
arry:that.data.arry
})
},
界面布局
<image src="{{arry[index] ? catagory.img : '/images/loading.gif'}}" ></image>
效果是实现了,但是页面列表的数据会根据不同条件的选择,列表的数量是不固定的,所以我们没有办法先写死false数组。然后我在下拉的过程中,发现数组的长度会自动往后加。本以为大功告成了,但是有一个bug,就是当我下拉的速度非常快的时候,会有一些位置没有补true,例:
查了资料才知道,因为onPageScroll方法是下拉监听函数,在下拉的过程中一直在执行,在这里面setData是非常耗性能的,所以把它做了延时。我们懒加载的目的就是优化性能,所以怎么能用耗性能的方法呢,我又找到了下面这种方法。
二、IntersectionObserver API
什么是IntersectionObserver?其实它就是创建了一个观察的视口,当你要观察的元素进入到这个视口,就可以对这个元素进行操作,这个是发异步的,所以不用考虑耗性能的问题啦。
这次的列表数据,每个图片都要加上show:false。举个栗子
let list = [
{
name:"aaaa",
img:"aa.jpg",
show:false
},
{
name:"bbbb",
img:"bb.jpg",
show:false
},
{
name:"cccc",
img:"cc.jpg",
show:false
},
]
列表数据大概就是这个意思。因为我的列表是发异步的到的,后面选择不同的条件,得到的列表数据会改变,所以我在setData的回调函数里进行操作的。
success: function (res) {
//数据获取成功后,先渲染一遍
self.setData({
list:res.data.list,
},function(){
//如果已经存在这个观察窗口,停止监听
if(self.intersectionObserver){
self.intersectionObserver.disconnect();
}
//遍历得到的数组,对其进行操作
for(let i in res.data.list){
//创建一个视口,观察多个元素
self.intersectionObserver = swan.createIntersectionObserver(self,{
observeAll: true,
});
//在视口里观察对应的元素,这边是.item0,.item1,.item2,.item3,...
self.intersectionObserver.relativeToViewport().observe('.item-'+i, (ret) => {
//ret.intersectionRatio > 0的意思是元素露出一点点
if(ret.intersectionRatio > 0){
res.data.list[i].show = true;//将列表里对应个数的图片显示出来
};
//将得到的新数组渲染到页面
self.setData({
list:res.data.list,
});
});
}
});
},
在页面退出后,要停止监听
onUnload: function() {
// 监听页面卸载的生命周期函数
let self = this;
if(self.intersectionObserver){
self.intersectionObserver.disconnect();
}
},
界面布局
<view s-for="{{list}}" s-key="i" s-for-item="n" class="item-{{index}}">
<view>
<image src="{{n.show ? n.logo : '/images/loading.gif'}}"></image>
</view>
</view>
这样做确实能实现效果,而且还不消耗性能。
问题来了
我页面中的list会根据条件的选择,获取到新的list进行刷新。第一次进入页面的时候完全没问题,然后在页面上选择了一个条件,得到一个新的list列表,但intersectionObserver观察到的不仅仅是这个新得的列表,还有我之前旧的列表。当我再选一个条件,intersectionObserver会观察3次的列表,而不是当前list。所以我怀疑是不是之前的视口没有关闭,一直在切换着观察,但我在创建之前已经把之前观察的窗口停止监听了self.intersectionObserver.disconnect();,打印出来看它的disconnect也是为true的。
我查了vue的intersectionObserver也是这么停止监听的,但在小程序里没有起到停止监听的作用,我不知道到问题出哪了,请看到这篇文章的大佬提点一下。