十万条数据怎么办?Vue3虚拟列表让你纵享丝滑

1. 技术背景

虚拟滚动(Virtual Scrolling)是一种优化长列表渲染性能的技术。当列表数据量很大时,传统方式渲染所有 DOM 元素会导致严重的性能问题。虚拟滚动通过只渲染可视区域内的元素,大幅减少 DOM 节点数量,从而提升渲染性能和用户体验。

2. 固定高度(v1)

2.1 核心实现原理

该组件基于以下核心思想实现:

  1. 通过一个固定高度的容器作为视口(viewport)
  2. 使用一个与全部数据等高的占位元素(scroll-bg)产生滚动条
  3. 动态计算并渲染可见区域内的数据项
  4. 利用 translate3d 实现高效的列表项定位

2.2 关键代码解析

模板结构

ini 复制代码
<template>
  <div class="viewport" ref="viewport" @scroll="handleScroll">
    <div class="scroll-bg" ref="scrollBg"></div>
    <div
      class="scroll-list"
      ref="scrollList"
      :style="{ transform: `translate3d(0, ${offset}px,0)` }"
    >
      <template v-for="item in visibleItems" :key="item.id">
        <slot :item="item"></slot>
      </template>
    </div>
  </div>
</template>

Props 定义

ini 复制代码
const props = defineProps<{
  items: any[]; // 列表数据
  remain: number; // 显示个数
  size: number; // 每个元素的高度
}>();

核心状态管理

用start和end来记录当前显示的屏幕需要显示的数组的起始位置,但是在滚动过程中会存在一个列表元素滚动了一部分的情况,会有空白的显示问题,所以在增加前后预加载的数据,相当于加载3个屏幕的数据;

用offset记录显示列表数据的父元素的偏移量,偏移量为用户滑动过完整的列表个数和列表高度的乘积;

对传入的总数据进行截取,只显示预加载和屏幕正在显示的列表数据,以此来提高渲染性能,提高用户体验;

ini 复制代码
// 数组的起始值
const start = ref(0);
const end = ref(props.remain);
// 数组渲染dom的偏移量
const offset = ref(0);

// 前面预先加载的个数
const prevCount = computed(() => {
  return Math.min(start.value, props.remain);
});
// 后面预先加载的个数
const nextCount = computed(() => {
  return Math.min(props.items.length - end.value, props.remain);
});
// 计算当前需要显示的数据
const visibleItems = computed(() => {
  const startIndex = start.value - prevCount.value;
  const endIndex = end.value + nextCount.value;
  return props.items.slice(startIndex, endIndex);
});

滚动事件处理

获取滚动的距离,重新计算屏幕中显示数据的开始位置和结束位置,更新偏移量

ini 复制代码
const handleScroll = () => {
  // 滚动的距离
  const scrollTop = viewportRef.value?.scrollTop ?? 0;
  // 滚动过去的完整个数
  const scrollCount = Math.floor(scrollTop / props.size);
  start.value = scrollCount;
  end.value = start.value + props.remain;
  offset.value = start.value * props.size - prevCount.value * props.size;
};

2.3 性能优化策略

  1. 只渲染可视区域内的元素,减少 DOM 节点数量
  2. 使用 translate3d 进行硬件加速,提高动画性能
  3. 预加载前后缓冲区数据,避免快速滚动时白屏
  4. 利用 Vue 的响应式系统精确更新数据切片

2.4 使用示例

ruby 复制代码
<template>
  <virtual-scroll-list :size="40" :items="items" :remain="10">
    <template v-slot="{ item }">
      <constant-item :title="item.title"></constant-item>
    </template>
  </virtual-scroll-list>
</template>

3. 动态高度(v2)

当列表的内容是不确定的,可变的时候,固定高度不再满足业务需求,需要增加可变的选项来满足可变高度的列表

3.1 核心实现原理

3.1.1 Position Cache(位置缓存)
  • 维护每个列表项的位置信息(top, bottom, height)
  • 初始时基于默认大小预估位置
  • 动态更新实际渲染后的准确位置
3.1.2 Binary Search(二分查找)
  • 在可变高度模式下,使用二分查找快速定位可见区域的起始索引
  • 相比线性查找大幅提升滚动性能

3.2 算法详解

3.2.1 二分查找算法

用于快速定位当前滚动位置对应的起始项索引:

区别于平常的二分算法,由于可能滚动的位置在一个列表元素的中间位置,所以增加temp变量来记录当前当前最上方显示的元素在数据列表中的索引;

ini 复制代码
const binarySearch = (scrollTop) => {
  let start = 0;
  let end = positions.length - 1;
  let temp = null;
  while (start <= end) {
    let mid = (start + end) | 0;
    let midBottom = positions[mid].bottom;
    if (scrollTop === midBottom) {
      return mid + 1;
    } else if (scrollTop < midBottom) {
      if (temp === null || temp > mid) {
        temp = mid;
      }
      end = mid - 1;
    } else {
      start = mid + 1;
    }
  }
  return temp || 0;
};
3.2.2 位置更新算法

当元素高度发生变化时,更新位置缓存:

ini 复制代码
  const { height } = el.getBoundingClientRect();
  const id = Number(el.getAttribute("vid")) || 0;
  const oldHeight = positions.find((p) => p.id === id)?.height ?? 0;
  const diffHeight = height - oldHeight;
  if (diffHeight !== 0) {
    // 高度有变化
    const index = positions.findIndex((p) => p.id === id);
    positions[index]!.height = oldHeight + diffHeight;
    positions[index]!.bottom = positions[index]!.top + height;
    //后面的都需要更新
    for (let i = index + 1; i < positions.length; i++) {
      positions[i]!.top = positions[i - 1]!.bottom;
      positions[i]!.bottom = positions[i]!.top + positions[i]!.height;
    }
  }

3.3 性能优化

3.3.1 节流处理

滚动事件使用lodash.throttle进行节流处理,默认100ms间隔。

3.3.2 预加载机制

根据前后预加载数量(prevCount/nextCount)渲染额外项,减少快速滚动时的白屏现象。

该组件可以轻松处理包含上万条数据的列表,同时保持流畅的滚动体验,是处理大数据列表渲染的理想解决方案。

团队介绍

智慧家技术平台-智家APP开发」通过持续迭代演进移动端一站式接入平台为三翼鸟APP、智家APP等多个APP提供基础运行框架、系统通用能力API、日志、网络访问、页面路由、动态化框架、UI组件库等移动端开发通用基础设施;通过Z·ONE平台为三翼鸟子领域提供项目管理和技术实践支撑能力,完成从代码托管、CI/CD系统、业务发布、线上实时监控等Devops与工程效能基础设施搭建。

相关推荐
Momo__1 小时前
Node Modules Inspector:Vue 团队出品的依赖分析神器
前端·vue.js·npm
ze^03 小时前
Day05 APP应用&微信小程序&原生态开发&H5+Vue技术&封装打包&反编译抓包点
vue.js·微信小程序·小程序
边界条件╝3 小时前
前端构建引擎:从模块解析到产物生成
前端·javascript·vue.js·react.js
愚者Pro15 小时前
Flutter Widget组件学习(专为 Uniapp 转 Flutter 定制)
vue.js·学习·flutter·uni-app
前端毕业班18 小时前
uniapp web 灵活控制 style scoped
前端·javascript·vue.js
卤蛋fg619 小时前
vxe-table 数据分组 + 单元格图表:柱状图与饼图渲染实战
vue.js
用户8417948145619 小时前
vxe-table 数据分组:三种展示方式详解
vue.js
LJA6484421 小时前
用 MCP + 一句话生成了完整的 CRUD 页面
vue.js
梵得儿SHI1 天前
Vue 项目实战与性能优化:工程化与协作全指南(规范 + 配置 + 协作 + 文档)
前端·vue.js·代码规范·eslint·团队协作·前端工程化·前端架构