H5移动端 Vue3 + vue-virtual-scroller 实现长列表性能优化

文章目录

移动端在渲染长列表时 大量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'

📢注意事项

  1. 整个列表的高度要写死,不然会将整改列表作为可视区域,会出现渲染全部数据而不是只渲染可视区域的问题;
    这里推荐使用 flex: 1; 实现,比 height: calc(100vh - 30vw); 更优雅、更易维护
  2. 使用 -webkit-overflow-scrolling: touch; 开启硬件加速,ios高版本自带
  3. 使用 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 事件,所以只能手写上拉加载

  1. 监听 DynamicScroller 滚动事件,如果当前距离顶部的值加上可视区域的值大于等于总高度,则滚动条触底,加载更多
  2. 使用 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()
    }
  }
}

兄弟们,上面的代码在项目中踩坑实测过了!!

源码就不贴了😄

相关推荐
赢乐7 天前
深度剖析:前端如何驾驭海量数据,实现流畅渲染的多种途径
element-plus·虚拟滚动·前端分页·分批渲染·大量数据的渲染·settimeout定时器·滚动触底加载数据