首先模拟一下优化前的聊天室
情况
(相关资料图)
肉眼可见的蛋疼~
但是优化还是得优化滴,不优化是不可能滴,但是在开始之前,我觉得有必要把优化步骤拆分为以下两点?
OK开工~
1、倒置scroll-view
为什么要倒置scroll-view
呢?从上面的第一点我们可以看出,如果需要正序地插入数据,那么就会不可避免地出现数据加载后无法显示后面数据
的情况,但是想要解决这种情况又需要使用scroll-into-view
属性,那么如果需要彻底地解决这个问题,就需要从问题的根源scroll-view
下手
首先是修改前
的代码?
这是一个直播画面 {{ item.data }}
const scrollIntoView = ref("index1");const upper = () => { let lastNum = scrollData.value[0].data; let newArr = []; for (let index = 1; index <= 10; index++) { newArr.push({ color: getRandomColor(), data: lastNum + index, index: `index${lastNum + index}`, }); } scrollData.value.unshift(...newArr.reverse()); // 这里可以使用nextTick来替换一下,结果也是一样的,但是为了更明显的回弹效果我使用了定时器 setTimeout(() => { scrollIntoView.value = `index${lastNum}`; console.log("scrollIntoView :>>", scrollIntoView.value); }, 100);};const getRandomColor = () => { return "#" + Math.random().toString(16).substr(2, 6);};
那么就先来试一下倒置scroll-view
到底也没有效果
首先我们需要给scroll-view
套上一个transform:rotate(180deg)
的属性,然后再给内部的子元素也套上同样的属性,别忘了给存放数据的数组
也倒置一下,最重要的,把scroll-view
上的scroll-into-view
属性去掉,就会得到这样的效果?
还有就是此时滚动条
的位置是在左边的,如果有需要可以使用CSS
属性去掉,或者自行模拟,下面是去去除滚动条的CSS样式
?
::-webkit-scrollbar { display:none; width:0; height:0; color:transparent;}
到这里还只是第一步
,下一步是如何下拉加载数据
。
此时我们的scroll-view
是处于倒置
的状态,也就是说顶部是底,底部才是顶
(搁着绕口令呢),所以之前使用的scrolltoupper触顶方法
要替换成scrolltolower触底方法
才能实现“下拉加载”
下面是目前的聊天室
看起来好多了
2、大量数据的处理
处理完回弹问题
后,就需要考虑如何处理大量数据
。由于uni-app
官方也在文档中提到scroll-view
加载大批量数据的时候性能较差,但无奈手头上也没有别的办法,只能死马当活马医了
我第一个想法就是非常经典的虚拟列表
,但是此前所看的很多关于虚拟列表的文章都是在web端
实现的,似乎小程序领域里并不是一个被经常采用的方法,但是所幸还是找到了如何在微信小程序实现虚拟列表
的资料,详情可以查看这篇文章?微信小程序虚拟列表
OK说干就干,那么第一步就是要明确实现虚拟列表需要什么样的数据结构
,虚拟列表其实简单地说就是当某一个模块的数据超出了可视范围就将其隐藏
,那么如何将数据分为多个模块呢?答案就是二维数组
首先将当前的页码
存储起来(默认为0),当触发下拉加载动作时页码+1
,然后以当前页码作为下标
存入数组
const currentShowPage=ref(0)const upper = () => { let len = scrollData.value[currentShowPage.value].length - 1; let lastNum = scrollData.value[currentShowPage.value][len].data; let newArr = []; currentShowPage.value += 1; for (let index = 1; index <= 10; index++) { newArr.push({ color: getRandomColor(), data: lastNum + index, index: `index${lastNum + index}`, }); } scrollData.value[currentShowPage.value] = newArr;};
当然别忘了在页面中也需要以二维数组
的形式循环数据
{{ item.data }}
数据结构
的问题解决了,那么接下来就是如何判断数据模块是否超出可视范围
。
首先我们需要知道每个数据模块的高度
,其实很简单,只需要为每个模块定义一个id
,然后在数据展示之后根据id
获取到该模块的节点信息
然后按顺序存储到数组中
即可
const pagesHeight = []onReady(()=>{ setPageHeight()})const upper = () => { ... nextTick(() => { // 每次获取新数据都调用一下 setPageHeight(); });};const setPageHeight = () => { let query = uni.createSelectorQuery(); query .select(`#item-${currentShowPage.value}`) .boundingClientRect(res => { pagesHeight[currentShowPage.value] = res && res.height; }) .exec();};
OK,现在我们已经知道每个模块的高度
了,然后就是监听模块与可视窗口的交叉范围
。这里有两种方法,一种是JS获取可视窗口的高度与模块scrollTop进行差值计算
,另一种是使用小程序的createIntersectionObserver方法让程序自行监听交叉区域
这里我展示的是第二种方法,如果对第一种方法感兴趣的朋友可以向上看第二章开头我推荐的《微信小程序虚拟列表》文章
关于createIntersectionObserver方法
的使用其实很简单,我们只需要把可视窗口的id
以及需要监听的模块id
传入即可,详情看官方文档
onReady(() => { ... observer(currentShowPage.value);});const upper = () => { ... nextTick(() => { // 每次获取新数据都调用一下 observer(); });};// 允许渲染的数组下标,需要设置默认值const visiblePagesList = ref([-1,0,1])const observer = pageNum => { const observeView = wx .createIntersectionObserver() .relativeTo("#scroll", { top: 0, bottom: 0 }); observeView.observe(`#item-${pageNum}`, res => { if (res.intersectionRatio > 0) visiblePagesList.value = [pageNum - 1, pageNum, pageNum + 1]; });};
最后就是在页面中判断该模块是否允许被渲染(也就是是否存储在visiblePagesList数组中)
,这里就很简单了,只需要写一个方法在页面中调用即可
{{ item.data }}
const includePage = index => { return visiblePagesList.value.indexOf(index) > -1;};
来看看效果如何
额...似乎没有太大区别,那我们看看页面结构到底也没有将可视区域外的内容切换为空白view
成功!
3、功能调整
聊天室原本还有回底功能
等,也不能忘了加上
这个部分就比较简单了,只需要直接使用scroll-view
的scroll-top属性
,然后通过在scroll回调中动态记载scroll-top的值即可
下面是部分代码
... 回底
let scrollTop;const currentTop = ref(0);const showGoBottom = ref(false);const handle_scroll = throttle(event => { scrollTop = event[0].detail.scrollTop; if (scrollTop > 300) { showGoBottom.value = true; }}, 100);const handle_goBottom = () => { currentTop.value = scrollTop; nextTick(() => { currentTop.value = 0; }); showGoBottom.value = false;};
大功告成~