评论

如何用小程序实现类原生APP下一条无限刷体验

详细记录了下一条无限刷效果的实现原理,以及细节和体验优化,并将相关代码抽象成一个微信小程序代码片段,有需要的同学可查看demo源码。

1.背景

如今信息流业务是各大互联网公司争先抢占的一个大面包,为了提高用户的后续消费,产品想出了各种各样的方法,例如在微视中,用户可以无限上拉出下一条视频;在知乎中,也可以无限上拉出下一条回答。这样的操作方式用户体验更好,后续消费也更多。最近几年的时间,微信小程序已经从一颗小小的萌芽成长为参天大树,形成了较大规模的生态,小程序也拥有了一个很大的流量入口。

2.demo体验

那如何才能在小程序中实现类原生APP效果的下一条无限刷体验?

这篇文章详细记录了下一条无限刷效果的实现原理,以及细节和体验优化,并将相关代码抽象成一个微信小程序代码片段,有需要的同学可查看demo源码。

线上效果请用微信扫码体验:

小程序demo体验请点击:https://developers.weixin.qq.com/s/vIfPUomP7f9a

3.实现原理

出于性能和兼容性考虑,我们尽量采用小程序官方提供的原生组件来实现下一条无限刷效果。我们发现,可以将无限上拉下一篇的文章看作一个竖向滚动的轮播图,又由于每一篇文章的内容长度高于一屏幕高度,所以需要实现文章内部可滚动,以及文章之间可以上拉和下拉切换的功能。

在多次尝试后,我们最终采用了在<swiper>组件内部嵌套一个<scroll-view>组件的方式实现,利用<swiper>组件来实现文章之间上拉和下拉切换的功能,利用<scroll-view>来实现一篇文章内部可上下滚动的功能。

所以页面的dom结构如下所示:

<swiper
  class='scroll-swiper'
  circular="{{false}}"
  vertical="{{true}}"
  bindchange="bindChange"
  skip-hidden-item-layout="{{true}}"
  duration="{{500}}"
  easing-function="easeInCubic"
>
  <block wx:for="{{articleData}}">
    <swiper-item>
      <scroll-view
        scroll-top="0"
        scroll-with-animation="{{false}}"
        scroll-y
      >
        content
      </scroll-view>
    </swiper-item>
  </block>
</swiper>

4.性能优化

我们知道view部分是运行在webview上的,所以前端领域的大多数优化方式都有用。例如减少代码包体积,使用分包,渲染性能优化等。下面主要讲一下渲染性能优化。

4.1 dom优化

由于页面需要无限上拉刷新,所以要在<swiper>组件中不断的增加<swiper-item>,这样必然会导致页面的dom节点成倍数的增加,最后非常卡顿。

为了优化页面的dom节点,我们利用<swiper>current<swiper-item>index来做优化,控制是否渲染dom节点。首先,仅当index <= current + 1时渲染<swiper-item>,也就是页面中最多预先加载出下一条,而不是将接口返回的所有后续数据都渲染出来;其次,对于用户已经消费过的之前的<swiper-item>,不能直接销毁dom节点,否则会导致<swiper>current值出现错乱,但是我们可以控制是否渲染<swiper-item>内部的子节点,我们设置了仅当current <= index + 1 && index -1 <= current时才会渲染<swiper-item>中的内容,也就是仅渲染当先文章,及上一篇和下一篇的文章内容,其他文章的dom节点都被销毁了。

这样,无论用户上拉刷新了多少次,页面中最多只会渲染3篇文章的内容,避免了因为上拉次数太多导致的页面卡顿。

4.2 分页时setData的优化

setData工作原理


小程序的视图层目前使用WebView作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebViewJavascriptCore 都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript 所实现。即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。

evaluateJavascript 的执行会受很多方面的影响,数据到达视图层并不是实时的。

  • 每次 setData 的调用都是一次进程间通信过程,通信开销与 setData 的数据量正相关。
  • setData 会引发视图层页面内容的更新,这一耗时操作一定时间中会阻塞用户交互。
  • setData 是小程序开发中使用最频繁的接口,也是最容易引发性能问题的接口。

避免不当使用setData

  • data 应仅包括与页面渲染相关的数据,其他数据可绑定在this上。使用 data 在方法间共享数据,会增加 setData 传输的数据量,。
  • 使用 setData 传输大量数据,通讯耗时与数据正相关,页面更新延迟可能造成页面更新开销增加。仅传输页面中发生变化的数据,使用 setData 的特殊 key 实现局部更新。
  • 避免不必要的 setData,避免短时间内频繁调用 setData,对连续的setData调用进行合并。不然会导致操作卡顿,交互延迟,阻塞通信,页面渲染延迟。
  • 避免在后台页面进行 setData,这样会抢占前台页面的渲染资源。可将页面切入后台后的setData调用延迟到页面重新展示时执行。

优化示例

无限上拉刷新的数据会采用分页接口的形式,分多次请求回来。在使用分页接口拉取到下一刷的数据后,我们需要调用setData将数据写进dataarticleData中,这个articleData是一个数组,里面存放着所有的文章数据,数据量十分庞大,如果直接setData会增加通讯耗时和页面更新开销,导致操作卡顿,交互延迟。

为了避免这个问题,我们将articleData改进为一个二维数组,每一次setData通过分页的 cachedCount标识来实现局部更新,具体代码如下:

this.setData({
  [`articleData[${cachedCount}]`]: [...data],
  cachedCount: cachedCount + 1,
})

articleData的结构如下:

4.3 体验优化

解决了操作卡顿,交互延迟等问题,我们还需要对动画和交互的体验进行优化,以达到类原生APP效果的体验。

在文章间上拉切换时,我们使用了<swiper>组件自带的动画效果,并通过设置durationeasing-function来优化滚动细节和动画。

当用户阅读文章到底部时,会提示下一篇文章的标题等信息,而在页面上拉时,由于下一篇文章的内容已经加载出来了,这样在滑动过程中会出现两个重复的标题。为了避免这种情况出现,我们通过一个占满屏幕宽高的空白<view>来将下一篇文章的内容撑出屏幕,并在滚动结束时,通过hidden="{{index !== current && index !== current + 1}}"来隐藏这个空白<view>,并对这个空白<view>的高度变化增加动画,来实现下一篇文章从屏幕底部滚动到屏幕顶部的效果:

.fake-scroll {
  height: 100%;
  width: 100%;
  transition: height 0.3s cubic-bezier(0.167,0.167,0.4,1);
}


而当用户想要上拉查看之前阅读过的文章时,我们需要给用户一个“下滑查看上一条”提示,所以也可以采用同上的方式,通过一个占满屏幕宽高的提示语<view>来将上一篇文章的内容撑出屏幕,并在滚动结束时,通过wx:if="{{index + 1 === current}}"来隐藏这个提示语<view>,并对这个提示语<view>的透明度变化增加动画,来实现下拉时提示“下滑查看上一条”的效果:

.fake-previous {
  height: 100%;
  width: 100%;
  opacity: 0;
  transition: opacity 1s ease-in;
}
.fake-previous.show-fake-previous {
  opacity: 1;
}

至此,这个类原生APP效果的下一条无限刷体验的需求的所有要点和细节都已实现。

记录在此,欢迎交流和讨论。

小程序demo体验请点击:https://developers.weixin.qq.com/s/vIfPUomP7f9a

最后一次编辑于  06-25  (未经腾讯允许,不得转载)
点赞 41
收藏
评论

19 个评论

  • 小程序技术专员-拉风
    小程序技术专员-拉风
    06-25

    效果很不错。

    06-25
    赞同 1
    回复 3
    • x_Qiang
      x_Qiang
      06-30

      在掘金看到同样文章,作者是腾讯的一个团队,谁是真的作者啊😂

      06-30
      回复
    • 吴晓晓🐇
      吴晓晓🐇
      07-01回复x_Qiang

      嗯,欢迎关注我们团队的专栏哦~

      07-01
      回复
    • x_Qiang
      x_Qiang
      07-01回复吴晓晓🐇

      好的!大佬

      07-01
      回复
  • 小师叔
    小师叔
    06-25

    setData的优化学到了

    06-25
    赞同 1
    回复
  • ieloag
    ieloag
    09-23

    这个有个问题....如果一页里面显示的条数过多,比如我一页里想显示100条数据..就会出现bug只会显示1条数据,其他的都是空白....容纳能力比普通的view要差..50条数据不到就撑不住了

    09-23
    赞同
    回复 3
    • 吴晓晓🐇
      吴晓晓🐇
      09-23
      不会会出现bug呀,你把100条数据放进一个swiper-item里就可以了。这里说的dom优化和setdata优化是一个示例,具体的应用场景还是要灵活实践。
      09-23
      回复
    • ieloag
      ieloag
      09-23回复吴晓晓🐇
      我再试试....界面效果做得挺棒...不能用就可惜了....
      09-23
      回复
    • ieloag
      ieloag
      09-23回复吴晓晓🐇
      测试了...不是bug..大概是机制位置...由于我的每条数据都含有图片是云数据库的图,而每页加载机制好像是等一页全部条数图片整个加载完再出现.....然后如果条数多了,就会出现一页等待时间特别长的状态,出只显示1条,其他都是空白的现象
      09-23
      回复
  • 十七
    十七
    09-11

    使用这个方法,滑动的时候,轻轻滑动然后松手,页面有一个回弹的效果,是swiper自带的,这个怎么去掉呢

    09-11
    赞同
    回复
  • ieloag
    ieloag
    09-08


    问个问题??  我是要在这个data里一次插入5个分页页面的数据吗??

    09-08
    赞同
    回复 1
    • 吴晓晓🐇
      吴晓晓🐇
      09-09
      是的,只是举个例子,具体一次插入几个分页面可以自己定。
      09-09
      回复
  • 💤
    💤
    09-04

    可以使用swiper实现所有图片的无限轮播吗,类似手机相册预览的效果

    09-04
    赞同
    回复 1
    • 吴晓晓🐇
      吴晓晓🐇
      09-09

      可以啊

      wx.previewImage({
              current: url,
              urls: this.imgList,
      })


      09-09
      回复
  • sw
    sw
    08-31

    一个页面就可以,为啥要搞两个页面,是不是增加代码包了?

    08-31
    赞同
    回复 1
    • 吴晓晓🐇
      吴晓晓🐇
      09-09
      不是两个页面,是一个页面,一个组件。把每篇文章抽象成一个组件方便在组件内做数据上报,以及评论数同步等逻辑
      09-09
      回复
  • 海贼
    海贼
    07-18

    我之前的做法与你这个类似,妹子很6啊

    07-18
    赞同
    回复
  • qq121
    qq121
    07-03

    学习了,很不错的文章

    07-03
    赞同
    回复
  • 济沧海
    济沧海
    06-29

    我想请教一下大神,您这个demo中,无限上下滑动都是相同的文字正文,怎样才能把相关的内容数据库嵌进去呢?

    06-29
    赞同
    回复 1
    • 吴晓晓🐇
      吴晓晓🐇
      07-01

      fetchData的时候拿到文章数据,然后setData呗


      07-01
      回复

正在加载...