小程序列表懒加载的实现方式

目录
  • 小程序上的列表懒加载
  • 分页加载
    • 用scroll事件监听
    • 用IntersectionObserver监听
  • 前端分页渲染
    • 用IntersectionObserver代替onReachBottom
    • 加需求!
  • demo源码
  • 参考

小程序上的列表懒加载

长列表我们经常接触到,长列表为什么需要懒加载呢,因为一旦渲染内容多了,渲染引擎就需要更多的时间去渲染画面,这时可能会出现页面白屏、卡顿等。而用户其实只需要看到视窗内的内容就可以了,不用一次性把全部内容渲染完,所以可以通过懒加载实现。

分页加载

常见的列表懒加载是和后端一起实现,也就是分页加载。前端请求第几页的数据,后端就返回第几页的数据。前端要实现的交互就是当用户滑动到页面底部时,就要请求下一页的数据。

用scroll事件监听

高度示意图

监听scroll事件,事件回调会有当前元素的滚动距离scrollTop,当scrollTop+screenHeight等于滚动高度scrollHeight时,表示已经滑动到底部。

在小程序中,Page对象提供onReachBottomapi

onReachBottom: function() {
  // 页面触底时执行
},

用IntersectionObserver监听

用滚动监听会非常耗性能,滚动时频繁触发回调的,所以会不断去计算判断。比较好的优化方案是IntersectionObserverAPI,当IntersectionObserver监听的元素与可视区有相交状态时,就会产生回调,这样就减少了触发的频率

Page({
  onLoad: function(){
    wx.createIntersectionObserver().relativeToViewport({bottom: 100}).observe('.target-class', (res) => {
      res.intersectionRatio // 相交区域占目标节点的布局区域的比例,不等于0时表示有相交
      res.intersectionRect // 相交区域
      res.intersectionRect.left // 相交区域的左边界坐标
      res.intersectionRect.top // 相交区域的上边界坐标
      res.intersectionRect.width // 相交区域的宽度
      res.intersectionRect.height // 相交区域的高度
    })
  }
})

前端分页渲染

上面说的都是前端结合接口的分页加载。假如说接口没有分页,直接就返回了庞大的数据列表。前端如果直接就setData所有数据,会渲染很久,其实复杂的列表渲染20条的时候就已经很慢了。这个时候需要对已经获取到的数据进行分页,分批进行渲染。

通过右侧面板可以看到,一开始没有渲染所有节点,在滑动页面过程中节点再不断增加。

直接上代码

<!-- pages/first/index.wxml -->
<view class="container">
  <block wx:for="{{goodsList}}" wx:key="index" wx:for-item="subItemList">
    <block wx:for="{{subItemList}}" wx:key="name">
      <view class="item">{{item.name}}</view>
    </block>
  </block>
</view>

goodsList是一个二维数组,用wx:for双重遍历

// pages/first/index.js
const list = Array.from({length: 5}, (item, index) => {
  return Array.from({length: Math.ceil(Math.random()*10 + 5)}, (subItem, subIndex) => {
    return {name: `第${index+1}屏, 第${subIndex+1}个`}
  })
})
/**
生成的list是一个二维数组
[
  [{}, {}],
  [{}, {}],
  [{}, {}],
  ...
]
**/

Page({
  data: {
    goodsList: [],
    lazyloadIdx: 0
  },
  onLoad() {
    this.setData({
      goodsList: [list[0]],
      lazyloadIdx: 1
    })
  },
  // 滑动到底部时往goodsList添加数据
  onReachBottom () {
    console.log('onReachBottom');
    let { lazyloadIdx } = this.data
    if (lazyloadIdx >= list.length) return
    this.setData({
      [`goodsList[${lazyloadIdx}]`]: list[lazyloadIdx],
      lazyloadIdx: lazyloadIdx+1
    })
  }
})

每次只setData一屏数据,既减少了setData数据量,又减少渲染时间

用IntersectionObserver代替onReachBottom

有很多场景用不了onReachBottom,那我们只能用IntersectionObserver代替。优化一下上面的代码

# pages/second/index.wxml
<view class="container">
  <block wx:for="{{goodsList}}" wx:key="index" wx:for-item="subItemList">
    <block wx:for="{{subItemList}}" wx:key="name">
      <view class="item">{{item.name}}</view>
    </block>
  </block>
+  <view id="observer" class="bottom"></view>
</view>

增加节点用来监听

//  pages/second/index.js
let lazyloadOb = null
Page({
  data: {
    goodsList: [],
    lazyloadIdx: 0
  },
  onLoad() {
    this.setData({
      goodsList: [list[0]],
      lazyloadIdx: 1
    })
    this.initObserver()
  },
  onunload () {
    this.disconnenct()
  },
  lazyloadNext () {
    console.log('lazyloadNext');
    let { lazyloadIdx } = this.data
    if (lazyloadIdx >= list.length) return
    this.setData({
      [`goodsList[${lazyloadIdx}]`]: list[lazyloadIdx],
      lazyloadIdx: lazyloadIdx+1
    })
  },
  initObserver () {
    lazyloadOb = wx.createIntersectionObserver().relativeToViewport({ bottom: 50 }).observe('#observer', (res) => {
      console.log('res.intersectionRatio', res.intersectionRatio);
      // 触发回调时加载下一屏
      if (res.intersectionRatio) this.lazyloadNext()
    })
  },
  disconnenct() {
    lazyloadOb.disconnenct()
  }
})

加需求!

后端返回的商品列表只是一个一维数组,需要前端转为二维数组,现在需要每屏的列表长度为5。

假设商品列表个数为21,那么会生成二维数组对应的子项长度:

// 伪代码
[5, 5, 5, 5, 1]

我们可以设定分页大小pageSize为5,当前分页pageNum,然后list.slice(pageNum, pageSize)就能截取对应的数据,再加入到二维数组中。

但是产品来加需求了,商品列表默认只展示非售罄商品+一个售罄商品,其余售罄商品要点击【查看更多】按钮才展示

假设非售罄商品有16个,售罄11个,每屏的列表长度还是5,那么:

[
  5, 5, 5,    // 非售罄
  2,          // 非售罄+售罄
  5, 5        // 售罄
]

只有两个的长度不大适合再分一屏,所以小于5时,直接跟前面的合并

[
  5, 5, 7, // 非售罄+一个售罄
  5, 5     // 售罄
]

这个时候设定pageSize就没法满足了,所以要根据售罄个数,非售罄个数以及一屏长度,算出长度数组,再算出对应的二维数组

/**
  * @desc 生成商品列表的子项长度
  * 展示列表包含售罄的,在非售罄列表最后拼接一个售罄商品,点击展开再加载售罄
  *
  * @param {number} onSaleLen 非售罄长度
  * @param {number} soldOutLen 售罄长度
  * @returns { { subSize: Array<number>; soldOutLen: number } }
  */
genSubListSize (onSaleLen, soldOutLen) {
  // 有售罄的时候,放一项售罄到非售罄那边去
  if (soldOutLen) {
    soldOutLen-= 1
    onSaleLen+=1
  }
  const arr = []
  const lazyloadListPartLength = 5 // 一屏5个
  let firstSize = 0
  if (onSaleLen < lazyloadListPartLength*2) {
    firstSize = onSaleLen
    onSaleLen = 0
  } else {
    firstSize = lazyloadListPartLength
    onSaleLen -= lazyloadListPartLength
  }
  arr.push(firstSize)

  // 子项长度
  const size = lazyloadListPartLength
  const remainder = onSaleLen % size
  arr.push(
    ...Array.from({length: Math.floor(onSaleLen/size) - 1}, () => size),
  )
  if (onSaleLen) {
    arr.push(onSaleLen <= size ? onSaleLen : size + remainder)
  }
  // 记录此时售罄项的索引,因为要点击展开才能加载售罄列表
  const soldOutIndex = arr.length
  if (soldOutLen) {
    const remainder = soldOutLen % size
    arr.push(
      ...Array.from({length: Math.floor(soldOutLen/size) - 1}, () => size),
      soldOutLen <= size ? soldOutLen : size + remainder
    )
  }

  console.log('genSubListSize', arr)

  return {
    subSize: arr,
    soldOutLen,
    soldOutIndex
  }
}
/**
  * eg: onSaleLen = 25; soldOutLen = 9; size = 5
  * return [5, 5, 5, 5, 6, 8]
  * eg: onSaleLen = 15; soldOutLen = 9; size = 5
  * return [5, 5, 6, 8]
  * eg: onSaleLen = 10; soldOutLen = 10; size = 5
  * return [5, 6, 9]
  * eg: onSaleLen = 14; soldOutLen = 10; size = 5
  * return [5, 5, 5, 9]
  * eg: onSaleLen = 8; soldOutLen = 9; size = 5
  * return [9, 8]
  * eg: onSaleLen = 2; soldOutLen = 10; size = 7 像这种小于非售罄小于size的,只能取到3了
  * return [3, 9]
**/

现在取列表长度为20,12个非售罄,8个售罄,一屏5个

// pages/third/index
const goodsList = Array.from({length: 20}, (item, index) => {
  return {name: `第${index+1}个`, soldOut: index < 12 ? false : true}
})
Page({
  // ...
  onLoad() {
    this.initObserver()
    this.handleGoodsList()
  },
  handleGoodsList () {
    const { onSaleLen, soldOutLen } = this.countSaleLen()
    console.log('onSaleLen', onSaleLen, 'soldOutLen', soldOutLen);
    const {
      subSize,
      soldOutLen: soldOutLength,
      soldOutIndex
    } = this.genSubListSize(onSaleLen, soldOutLen)
    const renderList = this.genRenderList(subSize)
    console.log('renderList', renderList);
  },
  countSaleLen () {
    const soldOutIndex = goodsList.findIndex(good => good.soldOut)
    if (soldOutIndex === -1) {
      return {
        onSaleLen: goodsList.length,
        soldOutLen: 0
      }
    }
    return {
      onSaleLen: soldOutIndex,
      soldOutLen: goodsList.length - soldOutIndex
    }
  },
  // 根据分组数组生成渲染列表
  genRenderList (subSize) {
    const before = goodsList
    const after = []
    let subList = [] // 二维数组子项
    let subLen = 0 // 子项长度
    let splitSizeArrIdx = 0 // 长度数组索引
    for (let i = 0; i < before.length; i++) {
      if (subLen === subSize[splitSizeArrIdx]) {
        splitSizeArrIdx++
        after.push(subList)
        subList = []
        subLen = 0
      }
      before[i]['part'] = `第${splitSizeArrIdx+1}屏`
      subList.push(before[i])
      subLen++
    }
    // 最后一个子项添加进去
    after.push(subList)
    return after
  }
})

打印一下renderList,得到了动态切分的数据了

列表分组

跑一下demo

当然需求是不断变化的,下次就不一定是售罄非售罄了,但是万变不离其宗,再怎么分,把每一项的数组长度定好之后,再生成渲染的renderList就可以了。所以可以把懒加载的这块逻辑抽离出来封装。

demo源码

以上三个demo的完整代码

参考

到此这篇关于小程序列表懒加载的文章就介绍到这了,更多相关小程序列表懒加载内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 小程序瀑布流组件实现翻页与图片懒加载

    电商小程序中,用到瀑布流的地方非常多,每次都写一个瀑布流,重复一次逻辑,作为程序员,肯定是非常不愿意的. 瀑布流的形式都是大同小异,不同的是瀑布流中每个模块的内容,随业务而变化. 所以,我们把瀑布流框架抽象成组件,瀑布流的内容由业务确定.这样即可实现组件化和自定义的最大平衡,微信小程序组件源码. 首先,我们来看一下瀑布流组件在实际项目中的实际效果. 1 实际效果 瀑布流组件实际效果如下图所示,左侧为用户交互效果,右侧为图片懒加载实际效果. 2 什么是瀑布流? 瀑布流,又称瀑布流式布局.是比较流行

  • 详解性能更优越的小程序图片懒加载方式

    意义 懒加载或者可以说是延迟加载,针对非首屏或者用户"看不到"的地方延迟加载,有利于页面首屏加载速度快.节约了流量,用户体验好 实现方式 传统H5的懒加载方式都是通过监听页面的scroll事件来实现的,结合viewport的高度来判断. 小程序也类似,通过监听页面onPageScroll事件获取当前滚动的数据,结合getSystemInfo获取设备信息来判断.由于scroll事件密集发生,计算量很大,经常会造成FPS降低.页面卡顿等问题. 这里说的是通过另外一种方式来实现 create

  • 微信小程序实现图片懒加载的示例代码

    本文主要介绍微信小程序的模拟图片懒加载,实现的原理是通过页面预加载图片(默认图),加载完成后再显示出来原图,而非真正意义上的懒加载(跟web的懒加载还有很大的差距),只是借此提高用户体验度. 多图片懒加载 1.xml页面 <block wx:for="{{list}}" wx:key=""> <image class='relative width-100 mgb-20 fade_in' src='{{item.cover_url}}' mode=

  • 小程序列表懒加载的实现方式

    目录 小程序上的列表懒加载 分页加载 用scroll事件监听 用IntersectionObserver监听 前端分页渲染 用IntersectionObserver代替onReachBottom 加需求! demo源码 参考 小程序上的列表懒加载 长列表我们经常接触到,长列表为什么需要懒加载呢,因为一旦渲染内容多了,渲染引擎就需要更多的时间去渲染画面,这时可能会出现页面白屏.卡顿等.而用户其实只需要看到视窗内的内容就可以了,不用一次性把全部内容渲染完,所以可以通过懒加载实现. 分页加载 常见的

  • 微信小程序上滑加载下拉刷新(onscrollLower)分批加载数据(一)

    微信小程序上滑加载下拉刷新(onscrollLower)分批加载数据(一) 页面缓存的设置优化了页面加载,减少了频繁的调取接口,使用户在断网的情况下有更好的体验. 每个微信小程序都可以有自己的本地缓存,可以通过 wx.setStorage(wx.setStorageSync).wx.getStorage(wx.getStorageSync).wx.clearStorage(wx.clearStorageSync)可以对本地缓存进行设置.获取和清理.本地缓存最大为10MB. 注意: localSt

  • 使用wxapp-img-loader自定义组件实现微信小程序图片预加载功能

    由于微信小程序没有提供类似 Image 这样的 JS 对象,要实现图片的预加载要麻烦一些, wxapp-img-loader自定义组件可以在微信小程序中实现图片预加载功能. 使用 1.下载 wxapp-img-loader项目源代码(https://github.com/o2team/wxa...),将 img-loader 目录拷贝到你的项目中 2.在页面的 WXML 文件中添加以下代码,将组件模板引入 <import src="../../img-loader/img-loader.w

  • 微信小程序下拉加载和上拉刷新两种实现方法详解

    方法一:onPullDownRefresh和onReachBottom方法实现小程序下拉加载和上拉刷新 首先要在json文件里设置window属性 设置js里onPullDownRefresh和onReachBottom方法 下拉加载说明: 当处理完数据刷新后,wx.stopPullDownRefresh可以停止当前页面的下拉刷新. onPullDownRefresh(){ console.log('--------下拉刷新-------') wx.showNavigationBarLoadin

  • 微信小程序实现自定义加载图标功能

    效果图 实现思路 1.首先通过HTML+CSS实现加载动画的静态效果: 2.根据需求给每个动画设计不同的动画效果. 例如第一个加载图标的静态绘制 1.首先确定动画的盒子宽高: 2.设置盒子中每一个长方形的宽高以及定位(注意:此处需要将长方形的旋转中心点移动到长方形的右侧边终点,方便后期以该点旋转.): 3.通过长方形盒子的伪元素,设置显示的长方形背景和宽高,同时进行定位. 4.由于在第二步的时候,已经将旋转中心移动,此处我们直接对每一个盒子中长方形进行旋转(注意:旋转角度 = 360 / 盒子中

  • 微信小程序实现滚动加载更多的代码

    1.需要用到的组件和api scroll-view(可滚动视图区域) wx.showToast(OBJECT)显示消息提示窗----显示loading小菊花用的 2.需要用到的属性 3.scrol-view需要指定一个高度,这个高度可以按自己的需求计算,我使用的是屏幕可用高度并且默认一页显示6个 4.滚动到底部绑定需要触发的事件 <scroll-view scroll-y='true' style="height:{{height}}px" bindscrolltolower='

  • 微信小程序实现分页加载效果

    分页加载功能大家遇到的应该会经常遇到,应用场景也很多,例如微博,QQ,微信朋友圈以及新闻类应用,都会有分页加载的功能,这不仅节省了我们用户的流量,还提升了用户体验.那么今天的这篇文章就是介绍微信小程序中如何实现分页加载的功能. 数据来自于后端(lumen带分页) /** * @todo 获取订单列表 * @return mixed */ public function getOrderList(){ $field = ['id','order_no','name','created_at','d

  • 微信小程序动态的加载数据实例代码

    微信小程序刚出来有很多的东西还在更新,先写些刚开始要做的工作 1.首先要写在js里定义一个全局变量 data: { datalist: [] }, 2.请求数据加载,获得整个数组信息 wx.request({ url: httpUrl, data: {}, success: function (res) { that.setData({ datalist: res.data.courses }) },  3.在.wxml中调用 数组的调用用:wx:for="{{datalist}}"

随机推荐