文章目录
移动端在渲染长列表时 大量dom节点的渲染和重绘重排会导致页面卡顿、滚动不流畅、设备耗电加快、影响移动设备电池寿命等性能问题
这里分享使用【虚拟滚动 】方案进行长列表优化,以Vue3为例,推荐使用 vue-virtual-scroller
先列举 vue-virtual-scroller
相关官方文档帮助学习
安装 vue-virtual-scroller
bash
npm install --save vue-virtual-scroller@next
bash
yarn add vue-virtual-scroller@next
引入
安装所有组件:
js
import VueVirtualScroller from 'vue-virtual-scroller'
app.use(VueVirtualScroller)
按需引入组件:
js
import { RecycleScroller } from 'vue-virtual-scroller'
app.component('RecycleScroller', RecycleScroller)
引入样式文件
js
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
📢注意事项
- 整个列表的高度要写死,不然会将整改列表作为可视区域,会出现渲染全部数据而不是只渲染可视区域的问题;
这里推荐使用flex: 1;
实现,比height: calc(100vh - 30vw);
更优雅、更易维护 - 使用
-webkit-overflow-scrolling: touch;
开启硬件加速,ios高版本自带 - 使用
overscroll-behavior: none;
禁用iOS回弹效果
使用
使用 DynamicScroller
组件渲染不确定高度的组件
基础使用
html
<div class="content-wrap">
<DynamicScroller
:items="dataList"
:min-item-size="160"
key-field="id"
class="virtual-scroller"
>
<template #default="{ item, index, active }">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.status, item.type]"
:data-index="index"
class="virtual-scroller-item"
>
<!-- 渲染组件 -->
<TaskItem :data="item" />
</DynamicScrollerItem>
</template>
</DynamicScroller>
</div>
css
.icc__container {
height: 100vh;
display: flex;
flex-direction: column;
box-sizing: border-box;
.icc__content-wrap {
flex: 1;
.virtual-scroller {
/* 开启硬件加速 -webkit-overflow-scrolling: touch; ios高版本自带 */
-webkit-overflow-scrolling: touch;
/* 禁用回弹效果 */
overscroll-behavior: none;
height: 100%;
}
}
}
上拉加载
vant List
+ DynamicScroller
会导致连续触发 vant List
load 事件,所以只能手写上拉加载
- 监听
DynamicScroller
滚动事件,如果当前距离顶部的值加上可视区域的值大于等于总高度,则滚动条触底,加载更多 - 使用
DynamicScroller
after
插槽,定义加载中、加载完成、加载失败等状态
html
<div class="content-wrap">
<DynamicScroller
:items="dataList"
:min-item-size="160"
key-field="id"
class="virtual-scroller"
@scroll="handleDynamicScrollerScroll"
>
<template #default="{ item, index, active }">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.status, item.type]"
:data-index="index"
class="virtual-scroller-item"
>
<!-- 渲染组件 -->
<TaskItem :data="item" />
</DynamicScrollerItem>
</template>
<template #after>
<div class="after">
<van-loading v-show="loadMoreLoading">加载中...</van-loading>
<span v-show="finished">没有更多了</span>
<span v-show="loadError" @click="handleLoadMore">请求失败,点击重新加载</span>
</div>
</template>
</DynamicScroller>
</div>
js
// 上拉loading
const loadMoreLoading = ref<boolean>(false)
// 没有更多数据了
const finished = ref<boolean>(false)
// 加载失败
const loadError = ref<boolean>(false)
// 实现上拉加载
const handleDynamicScrollerScroll = (e: Event) => {
// 距顶部
const scrollTop = (e.target as HTMLDivElement)?.scrollTop || 0
// 可视区高度
const clientHeight = (e.target as HTMLDivElement).clientHeight || 0
// 滚动条总高度
const scrollHeight = (e.target as HTMLDivElement)?.scrollHeight || 0
// 触底距离
const offset = 300
// 如果当前距离顶部的值加上可视区域的值大于等于总高度,则滚动条触底
if (scrollTop + clientHeight >= scrollHeight - offset) {
if (!loadMoreLoading.value && !finished.value && !loadError.value) {
console.log('滚动到底部了')
loadMoreLoading.value = true
handleLoadMore()
}
}
}
下拉刷新
使用 vant PullRefresh
实现下拉刷新
如果直接用 vant PullRefresh
包裹虚拟滚动,会导致无法向下滑动,任何位置下拉都会触发下拉刷新。
解决方案:
1. `vant PullRefresh` 中有 `disabled` 选项,可以禁用下拉刷新,默认设置为 `false`
2. 监听滚动条滚动事件中的 `scrollTop`,
3. 如果 `scrollTop` 小于4,则将 `disabled` 变为 `false`
4. 否则将 `disabled` 变为 `true`
html
<template>
<div class="container">
<van-pull-refresh
v-model="refreshLoading"
@refresh="handlerefresh"
:disabled="disabledPullRefresh"
class="content-wrap"
>
<template v-if="dataList.length > 0">
<DynamicScroller
:items="dataList"
:min-item-size="160"
key-field="id"
class="virtual-scroller"
id="virtual-scroller"
@scroll="handleDynamicScrollerScroll"
>
<template #default="{ item, index, active }">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.status, item.type]"
:data-index="index"
class="virtual-scroller-item"
>
<!-- 渲染组件 -->
<TaskItem :data="item" />
</DynamicScrollerItem>
</template>
<template #after>
<div class="after">
<van-loading v-show="loadMoreLoading">加载中...</van-loading>
<span v-show="finished">没有更多了</span>
<span v-show="loadError" @click="handleLoadMore">请求失败,点击重新加载</span>
</div>
</template>
</DynamicScroller>
</template>
<van-empty
v-else
image="./no_data.png"
description="暂无匹配数据"
:image-size="['60vw', 'auto']"
class="h-80vh"
/>
</van-pull-refresh>
</div>
<van-back-top target="#virtual-scroller" />
</template>
js
// 下拉loading
const refreshLoading = ref<boolean>(false)
// 禁用下拉刷新
const disabledPullRefresh = ref(false)
// 上拉loading
const loadMoreLoading = ref<boolean>(false)
// 没有更多数据了
const finished = ref<boolean>(false)
// 加载失败
const loadError = ref<boolean>(false)
// 实现上拉加载
const handleDynamicScrollerScroll = (e: Event) => {
// 距顶部
const scrollTop = (e.target as HTMLDivElement)?.scrollTop || 0
// 可视区高度
const clientHeight = (e.target as HTMLDivElement).clientHeight
// 滚动条总高度
const scrollHeight = (e.target as HTMLDivElement)?.scrollHeight
const offset = 300
// 如果直接用 `vant PullRefresh` 包裹虚拟滚动,会导致无法向下滑动,任何位置下拉都会触发下拉刷新。
// 控制是否开启下拉刷新
if (scrollTop <= 4) {
disabledPullRefresh.value = false
} else {
disabledPullRefresh.value = true
}
// 如果当前距离顶部的值加上可视区域的值大于等于总高度,则滚动条触底
if (scrollTop + clientHeight >= scrollHeight - offset) {
if (!loadMoreLoading.value && !finished.value && !loadError.value) {
console.log('滚动到底部了')
loadMoreLoading.value = true
handleLoadMore()
}
}
}
兄弟们,上面的代码在项目中踩坑实测过了!!
源码就不贴了😄