关于javascript:微信小程序仿instagram交互实现附长列表优化处理

40次阅读

共计 5718 个字符,预计需要花费 15 分钟才能阅读完成。

需要

最近几天在忙着搞公司我的项目的一个新的需要,起因是这样的:公司筹备开发一个偏差于社交娱乐项的小程序,其中首页是能够看到用户发的话题帖子之类的,每个帖子都至多蕴含一张图片或者一个视频,
而后产品那边心愿首页能够实现 instagram 的交互成果,效果图如下:

嗯,大抵上这个就是需要的背景,而后就是每个帖子的高度是不确定的,高度大略在 500~600px 之间。

实现思路

一开始接到这个需要,其实我心里还是有点慌的,毕竟有一段时间不怎么接触小程序,也不晓得小程序更新到什么水平,文档更新到什么水平。仔细分析一下我的项目需要,大抵上能够归类为两个:交互 和 性能优化。

性能优化

因为首页是一个长列表,家喻户晓,页面一旦渲染的节点过多,就会卡顿,更何况是小程序,并且小程序是分为逻辑层和渲染层,两者通过 setData 链接,所以解决的时候须要留神两点:

  1. setData 的数据量不能太大,记得如同是有个大小限度,,忘了是多少,也懒得找:clown_face:,你们能够本人在官网文档上找一下;
  2. 页面可能渲染的帖子数量是无限的,在这里,我是管制为最多渲染 25 个帖子。

解决

针对于长列表的优化,官网也有相应的组件 -recycle-view,然而貌似并不合乎我的项目需要,所以被我 pass 掉了。
尽管没用官网的组件,然而在组件的文档外面把对于长列表得性能优化解释一遍,这里摘抄一下重点:

​ 外围的思路就是只渲染显示在屏幕的数据,根本实现就是监听 scroll 事件,并且从新计算须要渲染的数据,不须要渲染的数据留一个空的 div 占位元素。

其实也就是设置一个变量管制该数据是否能够渲染,如果是不可能渲染得话,那咱们就用一个空的 view 取代它,须要留神的失败的是:空的框架高度须要设置为帖子的高度,这样子才不会闪屏。

针对这种思路,咱们就能够确定其中一种长列表的性能优化的解决思路:

  1. 将数据分为二维数组,这样子就能够限度每次 setData 的数据量,期待数据渲染实现之后,获取每组数据所占用的总高度,这里的高度是为了在改选数据不渲染时设置占位框的高度。

    /**
     * 获取 有渲染,然而高度还没获取到的分组 的高度
     */
    _getGroupListHeight() {this.data.list.forEach((item, index) => {if (item.show && !item.height) {
             const id = 'XXXXXXXX' // 组的 id
             let query = wx.createSelectorQuery()
    
             query.select(id).boundingClientRect(rect => {this.data.list[index].height = rect.height
             }).exec();
    
             this.getTopicHeight(item.data, index) // 获取列表中每个话题的高度,用于计算滑动时要滚动的间隔
         }
     })
    }

    下面就是一个简略获取每组的高度的代码实例,当该组数据有被渲染然而高度不明的状况下,就会去获取,加一步判断是为了避免重获获取组的数据,造成不必要的节约。在获取每组数据的高度时,还会对应去
    获取该组的每个帖子的高度,这样子是为了前面实现 仿 instagram 交互做筹备。

  2. 获取到了每组数据的高度,接下来,咱们就能够监听页面滚动的高度,从而管制须要渲染的数据,须要留神的一点是,咱们须要在该组的数据根底上,多渲染上两组和下两组数据,目标是避免用户疾速滑动的时候呈现白屏的不敌对体验。当然也能够依据本人的须要多渲染几组。

    /**
     * 页面滚动
     * @param e
     */
    onPageScroll(e) {// android 页面滑动解决(非仿 instagram 版本)
     if (!this.data.isIos && !this.data.scrollBoxInfo.canUseScrollBox) {
         // 1. 解决以后页面正在播放的视频
         if (this.data.currentPlayingId && Math.abs(e.scrollTop - this.data.scrollTop) > 100) {this.selectComponent(this.data.currentPlayingId).pauseVideo()
             this.data.currentPlayingId = ''
         }
    
         // 2. 解决 Andorid 渲染的分组数据
         this._dealAndroidScroll(e)
    
         // 3. 解决视频自动播放
         if (this.timer) {clearTimeout(this.timer)
         }
         this.timer = setTimeout(() => {
             this.data.scrollTop = e.scrollTop // 记录下以后的滚动间隔,滑动暂停视频播放的时候须要用到
             this.handleAutoPlay(e) // 视频自动播放
         }, 300)
     }
    }
    
    
    /**
     * Android 监听滚动,动静设置分组
     * @param {Object} e
     */
    _dealAndroidScroll(e) {
     let max_height = 0 // 最大高度
    
     for (let i = 0; i <= this.data.topicScroll.show_index; i++) {max_height += this.data.list[i].height
     }
     let min_height = max_height - this.data.list[this.data.topicScroll.show_index].height // 最小高度
    
     // 超过,+1
     if (e.scrollTop > max_height && this.data.topicScroll.show_index < this.data.list.length - 1) {
         ++this.data.topicScroll.show_index
         this._dealListShow(this.data.topicScroll.show_index)
     }
    
     // 小于,-1
     if (e.scrollTop < min_height) {
         --this.data.topicScroll.show_index
         this._dealListShow(this.data.topicScroll.show_index)
     }
    }

    这里没有对代码进行过滤,代码实现的成果就是监听滚动到的地位,并且设置渲染分组数据,因为有些是视频帖子,所以须要在滚动实现之后实现自动播放视频的性能。这段代码只是解决 anroid 平滑滚动的状况,因为代码波及到 instagram 交互的实现。

对于长列表性能优化的思路大抵上就这样:数据分为二位数组,渲染指定分组的数据,缩小渲染的数据量和须要渲染的节点数量,
不须要渲染的数据就用指定高度的空的 view 代替,指定高度是为了避免闪屏。

仿 instagram 交互实现

从下面的 instagram 交互视频能够看进去,咱们须要监听用户的手势滑动从而管制帖子的切换,并且每次切换只是切换一个帖子。晓得了交互的详情,咱们就能够开展设想了,大抵上能够给个根本的实现思路:

  1. 监听手指点击和手指来到的事件,记录下手指点击的高度 && 手指来到的高度,用来判断用户滑动的间隔和方向;记录下手指点击 和 手指来到的工夫,能够粗略用来判断用户以后的滑动行为是快滑还是慢滑。依据下面的失去的信息,咱们基本上实现滑动切换帖子的操作:

    /**
     * 监听手指点击操作
     * @param e
     */
    touchStart(e) {this.data.topicScroll.startTimeStamp = new Date().getTime() // 记录下以后手指点击事件
     this.data.topicScroll.startPosition = e.changedTouches[0].clientY // 记录下手指开始点击的地位
    },
    
    /**
     * 手指来到屏幕
     * @param e
     */
    touchEnd(e) {const diffTime = new Date().getTime() - this.data.topicScroll.startTimeStamp // 手指来到的时候的工夫戳
     const clientY = e.changedTouches[0].clientY // 手指来到屏幕的地位
     const diffY = Math.abs(clientY - this.data.topicScroll.startPosition) // 手指滑动的间隔
     const direction = this.data.topicScroll.startPosition - clientY > 0 // 手指滑动的方向,true 为向上滑,false 为向下滑
     const scrollInfo = this.data.topicScroll
    
     // 1. 第一个节点手指向下滑动 && 最初一个节点手指向上滑动 不做操作
     if (!scrollInfo.parent_index && !scrollInfo.child_index && !direction) {return} // 第一个节点向下滑动不做操作
     if (scrollInfo.parent_index === (this.data.list.length - 1) && scrollInfo.child_index === (this.data.list[scrollInfo.parent_index].data.length - 1) && direction) {return} // 最初一个节点向上滑动不做操作
    
    
     // 2. 依据滑动的方向,判断须要滚动到哪个节点下
     const can_move = (diffTime < 100 && diffY > 50) || (diffTime >= 100 && diffY > 80) // 是否能够滑动,手势滑动判断根据
     if (can_move) {if (direction) {if (scrollInfo.child_index === 4) {
                 ++scrollInfo.parent_index
                 scrollInfo.child_index = 0
             } else {++scrollInfo.child_index}
         } else {if (scrollInfo.child_index === 0) {
                 --scrollInfo.parent_index
                 scrollInfo.child_index = 4
             } else {--scrollInfo.child_index}
         }
     }
    
     // 3. 解决滚动
     this._dealScroll(can_move ? pausePlayId : '', can_move)
    }

    依据监听到信息,能够判断是否切换以及切换到那个帖子的操作,所以咱们只须要依据面曾经获取到的帖子高度就能够计算出来要滚动的高度。

  2. 依据第一点的操作,咱们就能够实现 仿 Instagram 交互成果,然而疏忽了致命的一点:页面的惯性滚动。也正是因为这个所谓的“惯性滚动”,我多花了几天的工夫去钻研交互的实现
    家喻户晓,为了使得页面的滑动更加晦涩,当咱们滑动进行的时候,页面就像会产生惯性个别,主动的滑动肯定间隔才停下。

    安卓下默认有惯性滚动,而在 iOS 下须要额定设置 -webkit-overflow-scrolling: touch 的款式

而第一点计划实现的大前提是页面不能领有惯性滚动,否则的话页面无奈精确滚动到指定地位。

解决方案

  1. ios 的解决方案比较简单,咱们只须要设置 -webkit-overflow-scrolling: auto 的款式即可。
  2. 比拟麻烦的是 android 的实现,一开始我是上网找了不少的材料去实现勾销页面的惯性滚动,毕竟页面滚动的性能是最好的。不过很惋惜,我没有找到可行的计划,所以我抉择退而求其次,模仿手指的滑动。通过对小程序文档的浏览,我抉择可两种比拟可能实现的计划:①应用 wxs 实现手指滑动的成果;②应用 scroll-view 的 fast-deceleration
    属性。不言而喻,应用 wxs 计划实现比较复杂,所以我一开始抉择 scroll-view 这个计划。

android 实现思路

scroll-view 计划实现的思路与 ios 相似,比拟不同的是页面的滚动,因为 ios 是能够间接应用页面滚动的,即 wx.pageScrollTo,而 scroll-view 则是应用 scroll-into-view 或者 scroll-top,为了缩小操作,我是间接应用了 scroll-top 这个属性。正当我信念满满的时候,做进去的成果却是打了我的脸:每次滚动到指定地位的时候都会抖动一下,尽管没有认真去查明起因,不过
我猜测是页面滚动仍然存在肯定的惯性滚动,当咱们设置了 scroll-top 的时候,惯性滚动的存在会使得列表偏移地位,最初在偏移回来,这样子看起来就像是抖动了一下。

正当我筹备放弃 scroll-view 这个计划的时候,无心中让我发现了 scroll-view 的 api 接口,本着想试一下新接口的心态,我尝试一下应用官网的 api 接口管制 scroll-view 滚动,神奇的是,成了!!!!

既然 scroll-view 做进去的成果勉强还能够,我就间接 pass 前面的 wxs 计划,毕竟那一块的逻辑应该会比较复杂。

因为时应用了 scroll-view 的 api 接口,反对的版本库比拟高,是 2.14.4,尽管大部分的用户能够反对,但还是要兼容少部分的用户,所以 android 局部我是搞了一个 scroll-view 版本和平滑滚动版本

fast-deceleration 这个个性在开发者工具那里貌似不失效,然而手机预览却是能够,不晓得是开发工具的问题还是说只是兼容了局部手机(我这边测试了小米和华为这两款,没更多的手机测试了:clown_face:)

demo

说了这么多,也不肯定有人看得上来,demo 呈上

结尾

在调试这个成果的时候,遇到不少的坑,其中有一个坑印象巨深,在此记录一下:

touch 期间 touchstart 的指标节点被移除,则对应的 touchend 事件会因为没有指标节点而缺失。

遇到这个坑是因为小程序同个页面不容许存在多个视频,所以须要将没有播放的视频应用图片替换,须要播放的视频的时候就替换回来,这样子,就会呈现下面的状况,导致无奈监听到 touchend 事件,整个列表停在原地,没有滚动到指定地位。

原文链接

传送门

正文完
 0