微信小程序答题页实现——swiper渲染优化

前言

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

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

  1. 保证swiper-item的数量固定,加载大量数据时,大大优化渲染效率
  2. 记录上次的位置,页面初次加载不一定非得是第一页,可以是任何页
  3. 答题卡选择某一index回来以后的数据替换,并去掉swiper切换动画,提升交互体验

微信小程序答题页实现——swiper渲染优化_第1张图片           微信小程序答题页实现——swiper渲染优化_第2张图片

问题原因

当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 pages = getCurrentPages();
    let prevPage = pages[pages.length - 2];
    // 改变之前的swiper的current
    let beforeChangeCurrent = prevPage.data.swiperCurrent
    let beforeChangeIndex = prevPage.data.swiperList[beforeChangeCurrent].index

    let index = e.currentTarget.dataset.index;
    // 进行取余,算出在swiperList的第几位
    let current = index % that.data.swiperListLength
    prevPage.setData({
      swiperList: util.getInitSwiperList(that.data.list, that.data.swiperListLength, index),
      swiperIndex: current,
      swiperCurrent: current,
    })
    // 改变之后的current和改变之前的current相等,index不同,就手动去调用一下swiperChange
    // 如果还是原来的那一项,不去调用swiperChange
    // 因为之前有在swiperChange中保存答题记录的操作,发现偶现记不上
    // 比如现在是第1题, swiperCurrent=0, 当你选择第4题, swiperCurrent还是=0, 对于swiper来说,并没有change
    if (current == beforeChangeCurrent && index != beforeChangeIndex) {
      let e = {detail: {current: current}}
      prevPage.swiperChange(e)
    }

    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差太远了吧。然后在网上和社区找也没有一个特别好的解决方案。后来想想,小程序是小程序,原生是原生。遇到需求就静下来解决吧。

项目地址:swiper-limited-load

如果错误,欢迎指出。

如果能帮到你们,记得给一个star,谢谢。

你可能感兴趣的:(微信小程序)