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


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中的parseInt使用技巧
Sep 03 Javascript
jquery 子窗口操作父窗口的代码
Sep 21 Javascript
使用SyntaxHighlighter实现HTML高亮显示代码的方法
Feb 04 Javascript
基于JavaScript实现继承机制之调用call()与apply()的方法详解
May 07 Javascript
jQuery fadeOut 异步实例代码详解
Aug 18 Javascript
Node.js数据库操作之连接MySQL数据库(一)
Mar 04 Javascript
用最简单的方法判断JavaScript中this的指向(推荐)
Sep 04 Javascript
vue项目中的webpack-dev-sever配置方法
Dec 14 Javascript
百度地图去掉marker覆盖物或者去掉maker的label文字方法
Jan 26 Javascript
玩转Koa之核心原理分析
Dec 29 Javascript
js设计模式之代理模式及订阅发布模式实例详解
Aug 15 Javascript
wx-charts 微信小程序图表插件的具体使用
Aug 18 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
火影忍者:这才是千手柱间和扉间的真正死因,角都就比较搞笑了!
2020/03/10 日漫
搜索和替换文件或目录的一个好类--很实用
2006/10/09 PHP
PHP 实现 WebSocket 协议原理与应用详解
2020/04/22 PHP
Javascript中的delete介绍
2012/09/02 Javascript
基于SVG的web页面图形绘制API介绍及编程演示
2013/06/28 Javascript
iframe中子父类窗口调用JS的方法及注意事项
2015/08/25 Javascript
JS实现课堂随机点名和顺序点名
2017/03/09 Javascript
angularJS开发注意事项
2018/05/26 Javascript
jQuery 实现倒计时天,时,分,秒功能
2018/07/31 jQuery
Vue 引入AMap高德地图的实现代码
2019/04/29 Javascript
js布局实现单选按钮控件
2020/01/17 Javascript
JQuery Ajax如何实现注册检测用户名
2020/09/25 jQuery
[03:27]最受玩家喜爱奖提名:PZH_Element 致玩家寄语
2016/12/20 DOTA
[59:30]VG vs LGD 2019国际邀请赛淘汰赛 胜者组 BO3 第二场 8.22
2019/09/05 DOTA
python中定义结构体的方法
2013/03/04 Python
python常规方法实现数组的全排列
2015/03/17 Python
Python使用reportlab将目录下所有的文本文件打印成pdf的方法
2015/05/20 Python
Python+Pika+RabbitMQ环境部署及实现工作队列的实例教程
2016/06/29 Python
python中numpy包使用教程之数组和相关操作详解
2017/07/30 Python
python中模块的__all__属性详解
2017/10/26 Python
python和shell监控linux服务器的详细代码
2018/06/22 Python
Python文件监听工具pyinotify与watchdog实例
2018/10/15 Python
Python实现二叉搜索树BST的方法示例
2019/07/30 Python
Python基于爬虫实现全网搜索并下载音乐
2021/02/14 Python
仿CSDN Blog返回页面顶部功能实现原理及代码
2013/06/30 HTML / CSS
项目经理聘任书
2014/03/29 职场文书
《风筝》教学反思
2014/04/10 职场文书
英语一分钟演讲稿
2014/04/29 职场文书
信息管理与信息系统专业求职信
2014/06/21 职场文书
2014年维稳工作总结
2014/11/18 职场文书
工商局调档介绍信
2015/10/22 职场文书
《童年》读后感(三篇)
2019/08/27 职场文书
nginx优化的六点方法
2021/03/31 Servers
SQLServer2019 数据库的基本使用之图形化界面操作的实现
2021/04/08 SQL Server
灵能百分百第三季什么时候来?
2022/03/15 日漫
Java多线程并发FutureTask使用详解
2022/06/28 Java/Android