1101 字
约 3 分钟
0
TanStack Virtual 入门:长列表虚拟滚动

TanStack Virtual 入门:长列表虚拟滚动

一万条数据的列表直接渲染,页面就死了。虚拟滚动的思路:只渲染看得见的那 20 条。

为什么需要虚拟滚动

DOM 不是免费的。每个列表项是一堆 DOM 节点,1 万条 × 每条 20 个节点 = 20 万节点,浏览器的布局、绘制、内存全面告急:滚动卡顿、点击延迟、手机直接发烫。

但有个关键洞察:用户的屏幕同时只能看到十几条。那 1 万条里 99.8% 的渲染纯粹是浪费。虚拟滚动的方案:

只渲染视口内 + 上下缓冲的条目(比如 20 条)
其余的用两个占位 div 撑起总高度
滚动时监听 scrollTop,动态换掉渲染窗口里的条目

对浏览器来说,这个列表永远是 20 个节点;对用户来说,和完整渲染毫无区别。万条如二十条。

TanStack Virtual 是什么

TanStack Virtual(前身 react-virtual,现支持 Vue/Svelte/Solid)是虚拟滚动的标准库实现,纯逻辑无 UI——它给你的是「当前应该渲染哪些条目」的计算结果,DOM 长什么样完全由你控制。本站前端用的是 Vue 适配包:

pnpm add @tanstack/vue-virtual

最小示例:定高列表

定高(每条高度一致)是最简单的场景:

<script setup>
import { ref } from 'vue';
import { useVirtualizer } from '@tanstack/vue-virtual';

// 假设有一万条数据
const items = ref(fetchTenThousandItems());

const parentRef = ref(null);

const virtualizer = useVirtualizer({
  count: items.value.length,
  getScrollElement: () => parentRef.value,   // 滚动容器
  estimateSize: () => 50,                    // 每条高度 50px
  overscan: 5,                               // 视口外上下各多渲染 5 条
});
</script>

<template>
  <!-- 外层:真实滚动容器,固定高度 -->
  <div ref="parentRef" class="list-container">
    <!-- 内层:撑起总高度的占位 -->
    <div :style="{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }">
      <!-- 只渲染虚拟窗口内的条目 -->
      <div
        v-for="row in virtualizer.getVirtualItems()"
        :key="items[row.index].id"
        :style="{
          position: 'absolute',
          top: 0,
          transform: `translateY(${row.start}px)`,
          height: `${row.size}px`,
          width: '100%',
        }"
      >
        {{ items[row.index].title }}
      </div>
    </div>
  </div>
</template>

<style>
.list-container { height: 600px; overflow-y: auto; }
</style>

三层结构是所有虚拟滚动的通用形:滚动容器(真实滚动)→ 总高占位(撑出滚动条长度)→ 绝对定位的窗口条目。virtualizer.getVirtualItems() 返回的永远是当前该渲染的那一小撮,带 index 和 start(该条应在的纵向偏移)。

动态高度:measureElement

条目高度不固定(聊天记录、文章列表)时,用测量模式:

<div
  v-for="row in virtualizer.getVirtualItems()"
  :key="row.key"
  :ref="(el) => virtualizer.measureElement(el)"
  :style="{ transform: `translateY(${row.start}px)` }"
>
  <div :class="内容高度不定">{{ items[row.index].content }}</div>
</div>

measureElement 会在每条渲染后实测其真实高度并回写虚拟器,滚动定位随之修正。首次滚动到深处可能有轻微跳动——高度是从估算逐步校准到实测的,这是动态高度虚拟滚动的固有代价,可接受。

滚动控制

// 跳到第 500 条(搜索定位、回到顶部等场景)
virtualizer.scrollToIndex(500, { align: 'start' });

这个 API 配合「搜索命中定位」「锚点跳转」非常好用——传统长列表想跳到第 N 条,只能先全量渲染再 scrollTop,虚拟列表下是一个函数调用。

本站的场景

本站后台用 @tanstack/vue-virtual 做长列表虚拟滚动,典型的如消息记录、日志流这类「条数会持续增长」的数据面板。配套要点:滚动容器要固定高度 + overflow: auto,条目 key 必须稳定(用数据 id,别用 index)——key 不稳定会导致滚动时组件复用错乱、输入框内容串行。

什么时候不需要它

虚拟滚动有成本:结构复杂化、动态高度微跳、与窗口化结合的动画(如 FLIP)会失效。所以:

  • 条目少于几百个 → 直接渲染,别过度工程;
  • 分页已经天然截断(每页 20 条)→ 不需要;
  • 无限滚动加载 + 条数会涨到几千上万 → 该上了。

小结

虚拟滚动的本质是用「渲染窗口」替代「全量渲染」,用空间换时间的反向操作。TanStack Virtual 把窗口计算这件事做到了框架无关的极致,UI 完全自持。记住三层结构和 measureElement 两个核心,万级列表的流畅渲染就有了。

TanStack Virtual 入门:长列表虚拟滚动
http://www.clxhxhhr.top/posts/3717/
作者
clxstart
发布于
2026-09-25
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。