前言
swiper的加载太多问题,网上资料好像没有一个特别明确的,就拿这个答题页,来讲讲我的解决方案
这里实现了如下功能和细节:
- 保证swiper-item的数量固定,加载大量数据时,大大优化渲染效率
- 记录上次的位置,页面初次加载不一定非得是第一页,可以是任何页
- 答题卡选择某一index回来以后的数据替换,并去掉swiper切换动画,提升交互体验
问题原因
当swiper-item数量很多的时候,会出现性能问题,我实现了一个答题小程序
在一次性加载100个swipe-item的时候,低端手机页面渲染时间达到了2000多ms
也就是说在进入答题页的时候,会卡顿2秒多去加载这个100个swiper-item
思考问题
那我们能不能让他先加载一部分,然后滑动以后再去改变item的数据,让swiper一直保持一定量的swiper-item?
注意到官方文档有这么两个属性可以利用,我们可以开启衔接滑动,然后再bindchange方法中去修改data
1、保证swiper-item的数量固定,加载大量数据时,优化渲染效率
假设我们请求到的数据的为list,实际渲染的数据为swiperList
假设我们现在给他就固定3个swiper-item,前后滑动的时候去替换数据
正向滑动的时候去替换滑动后的下一页数据,反向滑动的时候去替换滑动后的上一页数据
注意我们这里是采用循环衔接的方法,所以:
正向滑动成立的条件是:滑动到的这一页的index比上一页的index大或从swiperList的尾部滑动到头部
反向滑动成立的条件是:滑动到的这一页的index比上一页的index小或从swiperList的头部滑动到尾部
// 正向滑动,到下一个的时候
// 是正向衔接
let isLoopPositive = current == 0 && lastIndex == that.data.swiperList.length - 1
if (current - lastIndex == 1 || isLoopPositive) {
that.changeNextItem(current)
}
// 反向滑动,到上一个的时候
// 是反向衔接
var isLoopNegative = current == that.data.swiperList.length - 1 && lastIndex == 0
if (lastIndex - current == 1 || isLoopNegative) {
that.changeLastItem(current)
}
当我们知道了要替换的条件,我们便可以去替换数据了
替换数据应该确定:要被替换的swiperList的index和要从list中拿到的item的数据
但是我们应该考虑到临界值的问题,如果当前页是list第一项和最后一项该怎么办,向左向右滑是不是得禁止啊
我这里采用加了一个占位Item,是首位的占位让他弹回去,是末位的占位也让他弹回去,并且弹一个对话框(项目需求)
/**
* 获取swiperLit中current上一个的index
* 正常 - 1
* 或
* 循环衔接到末尾
*/
var getLastSwiperChangeIndex = function (current, swiperList) {
return current > 0 ? current - 1 : swiperList.length - 1
}
/**
* 获取上一个要替换的list中的item
*/
var getLastSwiperItem = function (current, swiperList, list) {
// swiperList所需要替换的index
let swiperChangeIndex = getLastSwiperChangeIndex(current, swiperList)
// list中我们需要的那个item的index
let listNeedIndex = swiperList[current].index - 1
console.log("上一个")
console.log("替换的swiper下标为:" + swiperChangeIndex + "," + "替换的list下标是:" + listNeedIndex)
// 如果要替换的下标超出了0,添加一个占位item
let item = listNeedIndex == -1 ? { isFirstPlaceholder: true } : list[listNeedIndex]
return item
}
/**
* 获取swiperLit中current下一个的index
* 正常 + 1
* 或
* 循环衔接到首位
*/
var getNextSwiperChangeIndex = function (current, swiperList) {
return current < swiperList.length - 1 ? current + 1 : 0
}
/**
* 获取下一个要替换的list中的item
*/
var getNextSwiperItem = function (current, swiperList, list) {
// swiperList所需要替换的index
let swiperChangeIndex = getNextSwiperChangeIndex(current, swiperList)
// list中我们需要的那个item的index
let listNeedIndex = swiperList[current].index + 1
console.log("下一个")
console.log("替换的swiper下标为:" + swiperChangeIndex + "," + "替换的list下标是:" + listNeedIndex)
// 如果要替换的下标超出了list的下标,添加一个占位item
let item = listNeedIndex == list.length ? { isLastPlaceholder: true } : list[listNeedIndex]
return item
}
在swiperChange方法中判断:
// 如果是滑到了左边界,再弹回去
if (that.data.swiperList[current].isFirstPlaceholder) {
that.setData({
swiperCurrent: lastIndex
})
wx.showToast({
title: "已经是第一题了",
icon:"none"
})
return
}
// 如果滑到了右边界,弹回去,再弹个对话框
if (that.data.swiperList[current].isLastPlaceholder) {
that.setData({
swiperCurrent: lastIndex,
// todo 弹个对话框
})
wx.showModal({
title: "提示",
content: "您已经答完所有题,是否退出?",
})
return
}
// 正向滑动,到下一个的时候
// 是正向衔接
let isLoopPositive = current == 0 && lastIndex == that.data.swiperList.length - 1
if (current - lastIndex == 1 || isLoopPositive) {
that.changeNextItem(current)
}
// 反向滑动,到上一个的时候
// 是反向衔接
var isLoopNegative = current == that.data.swiperList.length - 1 && lastIndex == 0
if (lastIndex - current == 1 || isLoopNegative) {
that.changeLastItem(current)
}
// 记录滑过来的位置,此值对于下一次滑动的计算很重要
that.data.swiperIndex = current
两个替换的方法:
changeNextItem: function (current) {
let that = this
let swiperChangeIndex = util.getNextSwiperChangeIndex(current, that.data.swiperList)
let swiperChangeItem = "swiperList[" + swiperChangeIndex + "]"
that.setData({
[swiperChangeItem]: util.getNextSwiperItem(current, that.data.swiperList, that.data.list)
})
},
changeLastItem: function (current) {
let that = this
let swiperChangeIndex = util.getLastSwiperChangeIndex(current, that.data.swiperList)
let swiperChangeItem = "swiperList[" + swiperChangeIndex + "]"
that.setData({
[swiperChangeItem]: util.getLastSwiperItem(current, that.data.swiperList, that.data.list)
})
},
2、记录上次的位置,页面初次加载不一定非得是第一页,可以是任何页
有很多时候,我们是从某一项直接进来的,比如说上次答题答到了第五题,我这次进来要直接做第六题
那么我们需要去初始化这个swiperList,让它当前页、上一页、下一页都有数据:
/**
* 获取初始化的swiperList
*/
var getInitSwiperList = function (list, swiperListLength, lastDoQuestionIndex) {
let swiperList = []
for (let i = 0; i < swiperListLength; i++) {
swiperList.push({})
}
// current
let current = lastDoQuestionIndex % swiperListLength
swiperList[current] = list[lastDoQuestionIndex]
// current的上一个
let lastSwiperIndex = getLastSwiperChangeIndex(current, swiperList)
swiperList[lastSwiperIndex] = getLastSwiperItem(current, swiperList, list)
// current的下一个
let nextSwiperIndex = getNextSwiperChangeIndex(current, swiperList)
swiperList[nextSwiperIndex] = getNextSwiperItem(current, swiperList, list)
console.log("工具类初始化的swiperList:")
console.log(swiperList)
return swiperList;
}
3、答题卡选择某一index回来以后的数据替换,并去掉swiper切换动画,提升交互体验
从答题卡选择index,那就不仅仅是滑动上下页了,它可以跳转到任何页面,所以也采用类似初始化swiperList的方法
去修改当前页、上一页、下一页的数据:
/**
* 点击答题卡的某一项
*/
onClickCardItem: function (e) {
let that = this;
let index = e.currentTarget.dataset.index;
let pages = getCurrentPages();
let prevPage = pages[pages.length - 2];
// 进行取余,算出在swiperList的第几位
let current = index % that.data.swiperListLength
let currentSwiperListItem = "swiperList[" + current + "]";
prevPage.setData({
swiperCurrent: current,
[currentSwiperListItem]: prevPage.data.list[index],
})
// 将前后的数据都改了
prevPage.changeNextItem(current)
prevPage.changeLastItem(current)
console.log(prevPage.data.swiperList)
wx.navigateBack({
delta: 1,
})
},
swiper切换动画我这边是默认250ms,但是发现有时候从答题卡点击回来,尤其跨度比较大的时候,比如跨了好几道题
那个动画哗哗哗好几页,体验真的很差,一开始不知道怎么禁掉动画,其实把duration设为0就可以了:
onClickAnswerCard: function(e) {
let that = this;
// 跳转前将动画去除,以免点击某选项回来后切换的体验很奇怪
that.setData({
swiperDuration: "0"
})
// 需要swiperListLength计算点击后的current
wx.navigateTo({
url: '../../pages/question_answer_card/question_answer_card?swiperListLength=' + that.data.swiperListLength,
})
},
在答题卡页的unload方法中恢复:
onUnload: function () {
// 销毁时恢复上一页的切换动画
let pages = getCurrentPages();
let prevPage = pages[pages.length - 2];
prevPage.setData({
swiperDuration: "250",
})
},
最后
我们可以设置swiperListLength的值,来控制你需要渲染多少个swiperItem,最少也得是三个
如果有做分页的需求,设置为10、20、任何值都可以
/**
* 页面的初始数据
*/
data: {
// 滑动到的位置
swiperIndex: 0,
// 此值控制swiper的位置
swiperCurrent: 0,
// 值为0禁止切换动画
swiperDuration: "250",
// 当前swiper渲染的items
swiperList: [],
// 需要几个swiperItem, 最少值为3,如果有分页的需求,可以是10、20, 任何
swiperListLength: 3,
list:[]
},
总结
一开始很头疼,为什么微信小程序提供的这个swiper,那么的不好用,和ViewPager差太远了吧。
然后在网上和社区找也没有一个特别好的解决方案。
后来想想,小程序是小程序,原生是原生。遇到需求就静下来解决吧。
项目地址:https://https://github.com/pengboboer/swiper-limited-load
如果错误,欢迎指出。
如果能帮到你们,记得给一个star,谢谢。