Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】

大家好,我是前端架构师 ,关注微信公众号【程序员大卫】,免费领取精品前端资料。

背景

本文将基于 Vue3 + Element Plus ,实现一个完全自定义的虚拟滚动表格方案 ,支持不等高行、缓存高度、缓冲区渲染,并且与 el-table 解耦。

Element Plus 虽然提供了虚拟滚动,但目前还是测试(beta)阶段,所以暂时先没用了,其实当时写这个组件主要是为了给 Element UI 使用的。

一、如何使用

在实际业务中,如果你正在使用 Element Plus 的 el-table ,又遇到了大数据量导致滚动卡顿的问题 ,那么这个组件可以在不改 el-table 任何代码 的前提下,为表格提供一套高性能的虚拟滚动能力。

使用方式非常简单:只需要用 VirtualListTable 包一层 el-table,并通过 change 事件接收当前需要渲染的数据即可。

  • VirtualListTable 负责滚动、计算可视区和缓冲区数据,
  • el-table 只负责展示当前这一小段数据,二者完全解耦。
html 复制代码
<VertualListTable :list-data="tableBigData" @change="renderVirtualData">
  <TableBigData :data="tableRenderData" />
</VertualListTable>

二、VirtualListTable 核心实现解析

1️⃣ 虚拟滚动的关键变量

ts 复制代码
const start = ref(0);                 // 当前起始索引
const cacheHeight = new Map();        // 行高缓存
let positions: Positions = [];        // 每一行的位置 & 高度
let scrollTop = 0;

positions 的结构:

ts 复制代码
{
  id: number | string,
  height: number,
  top: number
}

这是整个虚拟滚动的"地图"。

2️⃣ 可视区数据计算(含 buffer)

ts 复制代码
const visibleCount = computed(() =>
  Math.ceil(props.height / props.estimatedItemSize)
);

const visibleData = computed(() => {
  const startIndex = Math.max(start.value - props.bufferCount, 0);
  const endIndex = Math.min(
    start.value + visibleCount.value + props.bufferCount,
    props.listData.length,
  );
  return props.listData.slice(startIndex, endIndex);
});

为什么要 buffer?

  • 防止滚动时白屏
  • 提前渲染上下缓冲区域,提升体验

3️⃣ 滚动时如何快速定位起始索引(关键)

这里使用的是 二分查找。

ts 复制代码
const getStartIndex = (list: Positions, scrollTop: number) => {
  let index = null;
  let low = 0;
  let high = list.length - 1;

  while (low <= high) {
    const mid = low + ((high - low) >> 1);
    const midVal = list[mid]?.top;
    if (midVal <= scrollTop) {
      index = mid;
      low = mid + 1;
    } else {
      high = mid - 1;
    }
  }
  return index ?? 0;
};

时间复杂度从 O(n) 降到 O(log n),在大数据量下非常关键。

4️⃣ 使用 transform 控制真实 DOM 偏移

ts 复制代码
const setStartOffset = () => {
  const index = Math.max(start.value - props.bufferCount, 0);
  const offset = positions[index]?.top ?? 0;
  contentRef.value!.style.transform =
    `translate3d(0, ${offset}px, 0)`;
};
  • 不操作 top
  • 使用 transform,避免触发重排
  • GPU 加速,滚动更顺滑

5️⃣ 不等高行的核心:高度缓存 + ResizeObserver

ts 复制代码
const updateItemsSize = () => {
  getNodes().forEach((node) => {
    const id = getNodeId(node);
    const height = node.getBoundingClientRect().height;
    cacheHeight.set(id, height);
  });
};

结合 ResizeObserver:

ts 复制代码
ro = new ResizeObserver(() => {
  if (ignoreResize) return;
  updateLayout();
});

解决的问题:

  • 表格行高度动态变化
  • 文本换行、slot 变化
  • 不需要强制固定行高

6️⃣ 占位元素撑开滚动条

这是虚拟滚动中最核心的一步:DOM 只渲染几十行,但滚动条看起来像有几千行

html 复制代码
<div ref="placeholder" class="placeholder"></div>
ts 复制代码
const updateTotalHeight = () => {
  const lastItem = positions.at(-1);
  placeholderRef.value!.style.height =
    (lastItem.top + lastItem.height) + 'px';
};

三、与 el-table 的无侵入融合

  • 不改 el-table 源码
  • 只接管滚动容器
  • 所有表格功能照常使用(排序、列、样式)
ts 复制代码
const initElement = () => {
  const $wrapper = containerRef.value
    ?.querySelector('.el-table__body-wrapper');
  const $tableBody = $wrapper
    ?.querySelector('.el-scrollbar');

  contentRef.value?.appendChild($tableBody);
  $wrapper?.appendChild(scrollBoxRef.value!);
};

四、TableBigData:保持纯展示

vue 复制代码
<el-table :data="data">
  <el-table-column prop="name" label="Name" />
  <el-table-column prop="email" label="Email" />
</el-table>

五、总结

1. 这个方案适合什么场景

  • 超大数据量表格(1000+)
  • 行高不固定
  • 老项目 + Element Plus
  • 对滚动性能要求高

2. 方案优势

  • 支持不等高行
  • 与 el-table 解耦
  • 二分查找高性能
  • buffer 防白屏
  • ResizeObserver 自动修正

源码: github.com/zm8/wechat-...

相关推荐
zach23 分钟前
前端落地实战:Next.js 项目 Nginx + PM2 生产部署全流程(附踩坑总结)
前端·pm2·next.js
overmind32 分钟前
oeasy教h5前端001 修改的乐趣_直接修改在线网页
前端
只睡四小时37 分钟前
JS 手写 D-pad 空间导航:电视端焦点引擎实战
android·开发语言·前端·javascript·ecmascript·hls·空间导航
恋猫de小郭40 分钟前
Android 原生的 Compose A2UI 也来了,你还抱着 XML 养老吗?
android·前端·flutter
庄园特聘拆椅狂魔41 分钟前
从连连看到粒子消散——前端小游戏与视觉特效的技术选型
前端
JudithHuang1 小时前
React 常用 Hooks
前端·react.js·前端框架
sycmancia2 小时前
Qml——Window元素使用
前端
每天都好困2 小时前
03 AR 投影与误差修正:把地图上的点贴到视频画面上
前端
每天都好困2 小时前
04 地图图层封装:像管理 Photoshop 图层一样管理地图元素
前端
去伪存真2 小时前
构建你的第一个 DevOps AI Agent:自动化捕获、分析并修复 CI 故障
前端·agent