小程序多图列表实现性能优化的方法步骤


Posted in Javascript onMay 28, 2019

写这篇文章的缘由: 最近在公司的小程序项目中遇到了页面图片元素过多导致的性能问题. 从小程序提供的性能检测面板分析, 确定是图片元素占用了过多内存导致.

因为本人之前主要是做桌面端应用开发和原生app开发, 没有太顾及过移动端图片的内存占用问题. 这次既然遇到了, 也就趁这个机会学习一下其优化的技巧.

什么造成的性能问题

简单的来说: DOM节点过多 && 图片节点过多

DOM节点过多会造成更多的内存占用. 按照目前的微信小程序限制, 内存占用500M以上会出现卡顿, 甚至闪退. 如果列表中节点没有图片标签, 内存占用现象还不会太明显, 只是DOM节点过多会造成页面渲染耗时增加. 但当列表节点中含有图片时, 内存占用会迅速攀升.

如何解决这两点呢?

对于上面两点, 我们分别有对应的优化思路

1. DOM节点过多.

对于无限加载的页面, 列表中每一个元素都有大量的子节点. 当列表数目增加时, 页面的总节点数会暴增. 以小红书的小程序为例:

小程序多图列表实现性能优化的方法步骤

上图中可以看到, 该页面为很多的卡片组成的列表页面. 假设一个卡片的DOM子节点数为 30, 那么当列表元素加载到1000时, 页面会有 30 * 1000 = 30,000 个DOM节点. 小程序显然是吃不消的 (注: 微信小程序推荐总节点数不超过: 1000)

那我们该如何处理来减少节点数呢?

思路很简单: 我们可以只对用户当前屏幕和上下两屏进行真实内容的加载, 对于其他用户暂时不可见的地方, 用空白的节点进行占位. 这样处理后, 实际有内容的卡片数目不超过5个, 页面的总节点数为: 5 * 30 + 995 = 1145. 相对于之前的节点数有了巨大的改进.

让我们来看看代码的实现

写代码前, 让我们整理一下需要的数据结构.

首先这是一个列表页面, 我们需要一个 List来保存页面显示的数据: showCards. showCards 中只会保存5条真实数据, 其余数据展示以空对象填充.

我们还需要一个保存所有真实数据的List, 这样当用户滑动页面时, 我们才能实时获取需要显示的卡片真实数据: totalCards

Page({
 showCards: [],
 totalCards: []
})

接下来我们来写页面布局部分:

<view wx:for="{{showCards}}"
    wx:key="{{index}}">

  <self-define-component data-card-data="{{item}}">
  </self-define-component>
  
</view>

简单的代码框架就是这样 (这里省略了很多不影响理解思路的代码细节)

我们先实现没有优化DOM节点的代码逻辑. 在页面滑动到最底部时, 向showCards push进新的卡片, 并通过 setData 更新页面. 这样就实现了一个简单的下拉无限加载的列表页面.

async onReachBottom() {
  const newCards = await fetchNewCards();
 this.data.showCards.push(newCards);
 this.setData({
  showCards: this.data.showCards
 })
},

接下来我们实现优化DOM节点的代码逻辑. 我们会再用户滑动页面(onScroll事件) 时, 对当前页面每个card 的位置进行判断, 如果该 card在用户可见范围内的上下两屏内, 则展示真实数据, 否则将其替换为宽高与原卡片一致的空白占位节点.

在 Page 的 onPageScroll 回调中, 我们进行回收函数的调用 (注意这里回调时要进行节流处理, 否则频繁调用会导致页面闪动) . 让我们看看这个回收页面节点函数的主要逻辑:

回调中, 我们首先通过小程序提供的获取页面元素位置的api: createSelectorQuery().boundingClientRect 来拿到每个卡片的位置信息.

接下来, 我们通过位置信息, 判断是否展示card的真实数据. 对于不展示真实数据的card, 我们需要保存其高度信息, 以便在渲染页面时使用高度信息填充页面. 同时我们给空card一个 type 属性, 方便我们在 wxml中渲染时判断卡片类型.

async onScrollCallback() {
 try {
  const rectList = await this.calcCardsHeight();
  this.recycleCard(rectList);
 } catch (e) {
  console.error(e);
 }
}
 calcFeedHeight() {
  return new Promise((resolve, reject) => {
   this.createSelectorQuery()
    .selectAll(`.card`)
    .boundingClientRect(rectList => {
     resolve(rectList);
    })
    .exec()
  })
 },

 recycleCard(rectList) {
  const newShowCards = [];
  for (let i = 0; i < this.data.showCards.length; i++) {
   const rect = rectList[i];
   if (rect && Math.abs(rectList[i].top - 0) > pageHeight * 2) {
    newShowCards.push({
     type: 'empty-card',
     height: rectList[i].bottom - rectList[i].top
    });
   } else {
     const feed = totalCards[i];
    newShowCards.push(feed);
   }
  }
  this.setData({
   showCards: newShowCards
  });
 }

接下来, 我们要对wxml布局文件进行相应的修改:

<view wx:for="{{showCards}}"
    wx:key="{{index}}">

  <view wx:if="{{item.type === 'empty-card'}}"
     class="card empty-card"
     style="height: {{item.height}}px">
  </view>

  <self-define-component wx:if="{{item.type !== 'empty-card'}}"
        data-card-data="{{item}}"
        class="card read-card">
  </self-define-component>
  
 </view>

这样, 我们就解决了 DOM节点数目过多的问题. 并且最大限度的不影响用户的体验. (虽然用户快速上下滑动时还是会看到一些空白, 但大多数情况用户不会非常快速的上下滑, 而是阅读内容并慢速滑动)

2. 图片节点过多

通过上面一步的优化, 我们其实已经大幅减少了页面加载的图片数目. 但是有些情况, 我们的列表中的每一个卡片并不是只有一张图, 有时我们的图片组件是一个 swiper. 我们假设每个swiper平均展示10张图片, 那么我们展示5张card的话,<Image/> 节点就有 50 个. 对于一些低端的安卓机, 这样的开销依然会造成卡顿.

那有什么好的优化方案呢? 前面一个问题, 我们的优化思路是在用户看不见的地方, 将节点简化展示.

同样的, 对于swiper控件, 用户能看到的也就是当前图片 以及 滑动可见的左右两张图片. 其余位置的图片是可以简化展示的. 从下图可以看到, 其实需要立即加载的图片只有三张. (红色的框代表的是swiper组件的可视区域)

小程序多图列表实现性能优化的方法步骤

我们使用一个变量记录当前swiper控件展示图片的坐标: curIndex, 然后我们改造一下 wxml布局文件. 代码逻辑很简单, 就是通过判断当前Image 节点的index和swiper展示节点的 index之间距离, 大于2就不显示.

经过这样的处理后, 我们的每个swiper组件, 最多只会有三个占用实际内存的 <Image/> 节点.

<swiper-item wx:for="{{images}}"
          wx:key="{{index}}">

    <view >
     <image class="img"
         mode="widthFix"
         src="{{index - curIndex < 2 && index - curIndex > -2 ? item.url : ''}}">
     </image>
    </view>
   </swiper-item>

最后

以上就是我在这次性能优化中用到的一些小技巧, 希望能为你带来一些帮助 :)

如果你对我的文章感兴趣, 这里有我的一些 数据可视化, D3.js 方面的文章, 欢迎 fork && star:

https://github.com/ssthouse/ssthouse-blog

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
通过JAVASCRIPT读取ASP设定的COOKIE
Feb 15 Javascript
一个用javascript写的select支持上下键、首字母筛选以及回车取值的功能
Sep 09 Javascript
JS复制到剪贴板示例代码
Oct 30 Javascript
js获取判断上传文件后缀名的示例代码
Feb 19 Javascript
jquery 按键盘上的enter事件
May 11 Javascript
javascript数据结构之双链表插入排序实例详解
Nov 25 Javascript
javascript+HTML5自定义元素播放焦点图动画
Feb 21 Javascript
老生常谈Javascript中的原型和this指针
Oct 09 Javascript
巧用数组制作图片切换js代码
Nov 29 Javascript
jQuery插件FusionCharts绘制的2D帕累托图效果示例【附demo源码】
Mar 28 jQuery
小程序getLocation需要在app.json中声明permission字段
Apr 04 Javascript
浅谈JS for循环中使用break和continue的区别
Jul 21 Javascript
实例详解带参数的 npm script
May 28 #Javascript
jquery实现Ajax请求的几种常见方式总结
May 28 #jQuery
Vue2.x通用条件搜索组件的封装及应用详解
May 28 #Javascript
jquery操作select常见方法大全【7种情况】
May 28 #jQuery
vue实现条件叠加搜索的解决方法
May 28 #Javascript
webpack4 从零学习常用配置(小结)
May 28 #Javascript
详解ES6 export default 和 import语句中的解构赋值
May 28 #Javascript
You might like
配置php.ini实现PHP文件上传功能
2014/11/27 PHP
使用Laravel中的查询构造器实现增删改查功能
2019/09/03 PHP
jquery 操作iframe的几种方法总结
2013/12/13 Javascript
深入剖析JavaScript中的枚举功能
2014/03/06 Javascript
javascript学习笔记(一)基础知识
2014/09/30 Javascript
js获取会话框prompt的返回值的方法
2015/01/10 Javascript
JS使用正则表达式过滤多个词语并替换为相同长度星号的方法
2016/08/03 Javascript
原生JS轮播图插件
2017/02/09 Javascript
使用jQuery ajaxupload插件实现无刷新上传文件
2017/04/23 jQuery
Webpack中css-loader和less-loader的使用教程
2017/04/27 Javascript
AngularJs 常用的过滤器
2017/05/15 Javascript
MUI顶部选项卡的用法(tab-top-webview-main)详解
2017/10/08 Javascript
mpvue写一个CPASS小程序的示例
2018/09/04 Javascript
vue init webpack 建vue项目报错的解决方法
2018/09/29 Javascript
详解IOS微信上Vue单页面应用JSSDK签名失败解决方案
2018/11/14 Javascript
JavaScript设计模式之责任链模式实例分析
2019/01/16 Javascript
python爬虫教程之爬取百度贴吧并下载的示例
2014/03/07 Python
Python加密方法小结【md5,base64,sha1】
2017/07/13 Python
详解Python核心编程中的浅拷贝与深拷贝
2018/01/07 Python
python+matplotlib绘制饼图散点图实例代码
2018/01/20 Python
python 字典 按key值大小 倒序取值的实例
2018/07/06 Python
Python PyCharm如何进行断点调试
2019/07/05 Python
Django配置文件代码说明
2019/12/04 Python
pip安装提示Twisted错误问题(Python3.6.4安装Twisted错误)
2020/05/09 Python
django 解决扩展自带User表遇到的问题
2020/05/14 Python
关于HTML5你必须知道的28个新特性,新技巧以及新技术
2012/05/28 HTML / CSS
HTML5之SVG 2D入门11—用户交互性(动画)介绍及应用
2013/01/30 HTML / CSS
HTML5计时器小例子
2013/10/15 HTML / CSS
为什么UNION ALL比UNION快
2016/03/17 面试题
创业者迈进成功第一步:如何写创业计划书?
2014/03/22 职场文书
爱心捐款倡议书范文
2014/05/12 职场文书
导师就业推荐信范文
2014/05/22 职场文书
汽车维修专业自荐书
2014/05/26 职场文书
电子信息专业应届生自荐信
2014/06/04 职场文书
幼儿园安全管理制度
2015/08/05 职场文书
2019年二手房买卖合同范本
2019/10/14 职场文书