笔记: 移动端h5 vue3+vue-virtual-scroller加载不定高巨量数据,支持上拉加载,下拉刷新(配合vant PullRefresh)+ 分页加载以及避坑指南
笔记: 移动端h5 vue3+vue-virtual-scroller加载不定高巨量数据,支持上拉加载,下拉刷新(配合vant PullRefresh)+ 分页加载以及避坑指南
废话(不是): 项目是一个移动端的社区,可以发帖,可发布文字+图片(最多9张),之前直接搭的页面,通过分页加载数据,一次请求10条。后来产品那边反映在ios端会出现发热严重和掉电的情况。(部分原因: 社区首页是一直有兜底数据的,一直滑虽然分页,但dom会越堆越多。还有可能是定时器和监听器没关闭)问了下领导知道有"虚拟滚动/虚拟列表"这么个东西,网上找了几个成熟的库。
vue2:
- tangbc/vue-virtual-scroll-list
- Taro virtualscroll (组件库里的组件)
- Akryum / vue-virtual-scroller
vue3:
- reactjser / vue3-virtual-scroll-list (Forked from vue2 vue-virtual-scroll-list )
- tnfe / vue3-infinite-list
- Akryum / vue-virtual-scroller
项目结构
v-for(item, index) 渲染的dom,循环在父组件,子组件很多逻辑过度依赖循环的item,index,并且子组件Item高度完全不可控。自己造轮子失败,无法确定准确高度,想不通rem怎么动态设置。不知道该如何抽离子组件, Item多页面复用。
搜遍全网,这几个基本不适合我这个情况: vue3-virtual-scroll-list 子组件以函数形式传入,无法监听prop和emit事件,官方issue有人提过这个问题,作者建议通过eventBus等其他方式交互,Taro virtualscroll 支持index,并且支持插槽,可以正常写子组件,事件交互都不受影响,但是它的动态高度是写死的 比如官方的例子: getItemSize 是一个有如下语法的函数 : (i: number): number, 通过这个函数可以动态设置元素宽高.
const getItemSize = (i: number): number => {
switch (i % 4) {
case 1:
return 80;
case 2:
return 50;
case 3:
return 100;
default:
return 200;
}
};
官方也在git上给予我答复
只能放弃,最后用 Akryum / vue-virtual-scroller 的库基本实现需求。
Akryum / vue-virtual-scroller 的坑
如果是ios端H5,在这个库上滑/下滑滚动的时候,屏幕会闪动,并且严重的时候会中断滑动。Android/pc正常,很丝滑。
ios端h5解决方案/避雷:
- 浏览issue发现,降低版本可解决问题。我降到了2.0.0-beta.3
- 高度一定写死。不能用transform或者动画改变高度的值。不然闪动非常严重
- 开启硬件加速 -webkit-overflow-scrolling: touch; ios高版本自带
使用方法:
- 安装组件库 (上述问题降低版本至beta4以下)
npm install --save vue-virtual-scroller@next
- 全局引入/按需引入
//Install all the components:
import VueVirtualScroller from 'vue-virtual-scroller'
app.use(VueVirtualScroller)
//Use specific components:
import { RecycleScroller } from 'vue-virtual-scroller'
app.component('RecycleScroller', RecycleScroller)
- 引入组件并设置固定高度
<DynamicScroller
:items="items"
:min-item-size="54"
class="scroller"
>
<template v-slot="{ item, index, active }">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[
item.message,
]"
:data-index="index"
>
<div class="avatar">
<img
:src="item.avatar"
:key="item.avatar"
alt="avatar"
class="image"
>
</div>
<div class="text">{{ item.message }}</div>
</DynamicScrollerItem>
</template>
</DynamicScroller>
.scroller {
height: calc(100vh - 160px);
-webkit-overflow-scrolling: touch;
}
- 实现上拉加载 + loading图标(vant)
通过给虚拟滚动设置固定id,添加scroll监听
<DynamicScroller
:items="items"
:min-item-size="54"
class="scroller"
id="virtualScroller"
>
...
</DynamicScroller>
let virtualScroller = document.getElementById('virtualScroller')
virtualScroller ? virtualScroller.addEventListener('scroll', handleScroller) : ''
监听事件中判断是否触底 因为是移动端 用 scrollTop + clientHeight + 1 >= scrollHeight判断
const handleScroller = (e) => {
if (scrollTop + clientHeight + 1 >= scrollHeight) {
// virtualScrollerAllow.value = true // 触底逻辑
}
}
坑
ios触底会回弹,导致疯狂触发触底的逻辑,建议用一个单独变量控制是否到底,再watch监听这个变量,true/false 来判断是否执行触底逻辑 (可能影响性能)
加入 “加载中/加载失败/到底文案及图标”,vant中list组件 虚拟滚动完算是废了😂 页面没有滚动,根本达不到vant触发逻辑,并且ios橡皮筋回弹会导致list鬼畜式触发。 如果h5依赖原生客户端,可以客户端方关闭并和h5通信,单h5页面网上方法好像并不能完全关闭橡皮筋效果。
通过插槽手动写个触底状态。 插槽有两个 before和after 在顶部和底部。 触底定义变量,通过监听变量的值请求接口,再请求接口回来根据改变变量的值
<DynamicScroller
:items="listData"
:min-item-size="100"
class="scroller"
id="virtualScroller"
v-else
>
<template v-slot="{ item, index, active }">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[
item.imageUrlList, item.content
]"
:data-index="index"
>
//<Item :infos="item" @follow="onFollowClick(item,index)" :routerStatus="routerStatus" :currentIndex="index" :pageIdentify="pageIdentify" @like="onLikeClick(item, index)" @collection="onCollectionClick(item, index)" @commit="onCommitClick" @report="onReportClick" @delete="onDeleteClick(item,index)" @onDetailClick="sessionStorageWatch"></Item>
</DynamicScrollerItem>
</template>
<template #after>
<div class="after-loading">
<van-loading size="24px" class="status-loading" v-if="loadingStatus==='loading'">加载中...</van-loading>
<span class="status" v-if="loadingStatus==='error'" @click="loadError">加载失败,点击重新加载</span>
<span class="status" v-if="loadingStatus==='finished'">到底了~</span>
</div>
</template>
</DynamicScroller>
5.借助vant PullRefresh 实现下拉刷新
vant PullRefresh 中有disabled的选项,可以禁止下拉刷新,如果直接用PullRefresh包裹虚拟滚动,会导致无法向下滑动,任何位置下拉都会触发刷新逻辑。先用disabled禁用下拉刷新,通过监听虚拟滚动scroll中scrollTop,如果小于10或者等于0 (ios回弹还是体验不好) 将 disabled变为false
一直监听scroll也在影响性能
- 实现分页加载
只需在触底逻辑时,执行分页加载,并且新数据回来时,往原数组添加
/*const getIndexContent = async (isFirst) => {
loadingStatus.value = 'loading'
try {
let topRes, baseRes
if(isFirst) {
topRes = await getIndexTop()
indexTops.value.push(...topRes)
}
baseRes = await getIndexList()
indexBases.value.push(...baseRes)
if (indexTops.value.length === 0 && indexBases.value.length === 0) {
loadingStatus.value = 'finished'
} else {
*/
indexList.value = [
...indexTops.value,
...indexBases.value
]
/*
virtualScrollerAllow.value = false
// 活动上报
reportAdd(indexList.value.reduce((pre,cur)=>{
pre.push(cur.id)
return pre;
},[]))
}
} catch (e) {
console.log('获取首页内容有误', e)
loadingStatus.value = 'error'
}
}
*/
更多推荐
所有评论(0)