微信小程序答题页实现——swiper渲染优化
发布于 5 年前 作者 mamin 5584 次浏览 来自 分享

前言

swiper的加载太多问题,网上资料好像没有一个特别明确的,就拿这个答题页,来讲讲我的解决方案

这里实现了如下功能和细节:

  1. 保证swiper-item的数量固定,加载大量数据时,大大优化渲染效率
  2. 记录上次的位置,页面初次加载不一定非得是第一页,可以是任何页
  3. 答题卡选择某一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,谢谢。


1 回复

非常感谢,我也开发了一个在线答题的小程序,有计划把答题的过程用swiper改造下,看来这就是我要找的答案了,再次感谢。

回到顶部