vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Vue3 Element-Plus虚拟表格滚动

基于Vue3+Element-Plus实现自定义虚拟表格滚动的方案

作者:程序员大卫

本文介绍了如何基于Vue3和ElementPlus实现一个自定义的虚拟滚动表格方案,该方案支持不等高行、缓存高度、缓冲区渲染,并且与el-table解耦,通过使用VirtualListTable组件,可以在不修改el-table代码的前提下,为表格提供高性能的虚拟滚动能力,需要的朋友可以参考下

背景

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

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

一、如何使用

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

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

<script>
// 渲染虚拟数据
const renderVirtualData = (data)=>{
    tableData.value = data;
}
</script>

<VertualListTable :list-data="largeData" @change="renderVirtualData">
    <el-table :data="tableData">
        <el-table-column prop="name" label="Name" />
        <el-table-column prop="address" label="Address" />
    </el-table>
</VertualListTable>

二、VirtualListTable 核心实现解析

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

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

positions 的结构:

{
  id: number | string,
  height: number,
  top: number
}

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

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

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️⃣ 滚动时如何快速定位起始索引(关键)

这里使用的是 二分查找

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 偏移

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)`;
};

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

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

结合 ResizeObserver

ro = new ResizeObserver(() => {
  if (ignoreResize) return;
  updateLayout();
});

解决的问题:

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

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

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

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

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:保持纯展示

<el-table :data="data">
  <el-table-column prop="name" label="Name" />
  <el-table-column prop="email" label="Email" />
</el-table>

五、总结

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

2. 方案优势

以上就是基于Vue3+Element-Plus实现自定义虚拟表格滚动的方案的详细内容,更多关于Vue3 Element-Plus虚拟表格滚动的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文