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


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读取中文COOKIE的解决办法
Feb 15 Javascript
javascript获取选中的文本的方法代码
Oct 30 Javascript
JS获取地址栏参数的几种方法小结
Feb 28 Javascript
标题过长使用javascript按字节截取字符串
Apr 24 Javascript
jQuery中insertBefore()方法用法实例
Jan 08 Javascript
javascript倒计时效果实现
Nov 12 Javascript
Vue中的slot使用插槽分发内容的方法
Mar 01 Javascript
如何手动实现es5中的bind方法详解
Dec 07 Javascript
node中IO以及定时器优先级详解
May 10 Javascript
bootstrap中的导航条实例代码详解
May 20 Javascript
微信小程序canvas绘制圆角base64图片的实现
Aug 18 Javascript
让JavaScript代码更加精简的方法技巧
Jun 01 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 adodb操作mysql数据库
2009/03/19 PHP
PHP表单递交控件名称含有点号(.)会被转化为下划线(_)的处理方法
2013/01/06 PHP
php使用strip_tags()去除html标签仍有空白的解决方法
2016/07/28 PHP
基于jQuery捕获超链接事件进行局部刷新代码
2012/05/10 Javascript
深入解析JavaScript中的变量作用域
2013/12/06 Javascript
JavaScript cookie的设置获取删除详解
2014/02/11 Javascript
Javascript正则控制文本框只能输入整数或浮点数
2014/09/02 Javascript
node.js中的fs.chownSync方法使用说明
2014/12/16 Javascript
jquery实现TAB选项卡鼠标经过带延迟效果的方法
2015/07/27 Javascript
基于jQuery实现的无刷新表格分页实例
2016/02/17 Javascript
Vue中计算属性computed的示例解读
2017/07/26 Javascript
vue.js组件vue-waterfall-easy实现瀑布流效果
2017/08/22 Javascript
nodejs创建简易web服务器与文件读写的实例
2017/09/07 NodeJs
JavaScript使用递归和循环实现阶乘的实例代码
2018/08/28 Javascript
js实现简单分页导航栏效果
2019/06/28 Javascript
Node.js 实现远程桌面监控的方法步骤
2019/07/02 Javascript
微信小程序开发之map地图组件定位并手动修改位置偏差
2019/08/17 Javascript
layui form表单提交之后重新加载数据表格的方法
2019/09/11 Javascript
Vue Object.defineProperty及ProxyVue实现双向数据绑定
2020/09/02 Javascript
[06:07]刀塔密之二:攻之吾命受之吾幸
2014/07/03 DOTA
python登录QQ邮箱发信的实现代码
2013/02/10 Python
Python语言生成水仙花数代码示例
2017/12/18 Python
Python利用matplotlib做图中图及次坐标轴的实例
2019/07/08 Python
python3中替换python2中cmp函数的实现
2019/08/20 Python
用python读取xlsx文件
2020/12/17 Python
HTML5梦幻之旅——炫丽的流星雨效果实现过程
2013/08/06 HTML / CSS
香港零食网购:上仓胃子
2020/06/08 全球购物
JAVA程序员面试题
2012/10/03 面试题
会计毕业生自我鉴定
2013/11/04 职场文书
《大自然的语言》教学反思
2014/04/08 职场文书
毕业实习自我鉴定范文2014
2014/09/26 职场文书
党的群众路线教育实践活动领导班子整改措施
2014/10/28 职场文书
承诺函格式模板
2015/01/21 职场文书
保管员岗位职责
2015/02/14 职场文书
Python中Numpy和Matplotlib的基本使用指南
2021/11/02 Python
安装Ruby和 Rails的详细步骤
2022/04/19 Ruby