Lzh on GitHub
<template>
  <div>
    <p>
      这些组件使用的是 <strong>可变</strong> 尺寸。这意味着每个元素在渲染时都具有独一无二但已知的尺寸。
    </p>
    <br />
    <br />

    <h3>Rows</h3>
    <RowVirtualizerVariable :rows="rows" />
    <br />
    <br />
    <h3>Columns</h3>
    <ColumnVirtualizerVariable :columns="columns" />
    <br />
    <br />
    <h3>Grid</h3>
    <GridVirtualizerVariable :rows="rows" :columns="columns" />
    <br />
    <br />
    <h3>Masonry (vertical)</h3>
    <MasonryVerticalVirtualizerVariable :rows="rows" />
    <br />
    <br />
    <h3>Masonry (horizontal)</h3>
    <MasonryHorizontalVirtualizerVariable :columns="columns" />
  </div>
</template>

<script setup lang="ts">
import RowVirtualizerVariable from './RowVirtualizerVariable.vue'
import ColumnVirtualizerVariable from './ColumnVirtualizerVariable.vue'
import GridVirtualizerVariable from './GridVirtualizerVariable.vue'
import MasonryVerticalVirtualizerVariable from './MasonryVerticalVirtualizerVariable.vue'
import MasonryHorizontalVirtualizerVariable from './MasonryHorizontalVirtualizerVariable.vue'

// 创建包含10000个元素的行高度数组
// 每个元素的值是25到125之间的随机整数(25 + 0-100的随机数)
const rows = new Array(10000)
  .fill(true) // 先用占位符填充数组
  .map(() => 25 + Math.round(Math.random() * 100)) // 生成25-125的随机高度值

// 创建包含10000个元素的列宽度数组
// 每个元素的值是75到175之间的随机整数(75 + 0-100的随机数)
const columns = new Array(10000)
  .fill(true) // 先用占位符填充数组
  .map(() => 75 + Math.round(Math.random() * 100)) // 生成75-175的随机宽度值
</script>

lanes 选项

useVirtualizer(以及其核心 Virtualizer 类)中,lanes 选项是一个非常强大的工具,它允许你将一个虚拟化的列表 逻辑上划分为多个并行的“车道”。这通常用于创建更复杂的布局,如 瀑布流(Masonry Layouts) 或多列/多行的网格。

lanes 选项的作用

简单来说,lanes 选项让你能把一个原本是一维滚动(要么是垂直一长列,要么是水平一长行)的列表,变成一个 二维的排列,但它仍然只通过一个滚动轴来控制。

  • 垂直滚动列表(默认方向): 当你使用默认的垂直滚动时,lanes 代表你希望列表有多少 。例如,lanes: 3 会将你的所有项目逻辑上分配到 3 列中。
  • 水平滚动列表(设置 horizontal: true: 如果你将 horizontal 选项设置为 true,那么 lanes 就代表你希望列表有多少

为什么需要 lanes

传统上,如果你想创建一个多列或多行的动态高度/宽度列表,你需要手动计算每个项目的位置,这会非常复杂,尤其是在虚拟化场景下。lanes 选项配合 TanStack Virtual 的内部逻辑,大大简化了这一过程:

  1. 自动布局与填充: Virtualizer 会尝试智能地将项目分配到各个 lane 中,目标是使每个 lane 的总高度(或宽度,取决于方向)保持相对平衡,从而避免出现某列/行过长而其他列/行为空的情况。这对于瀑布流布局尤其重要。
  2. 性能优化: 即使是多列/行布局,TanStack Virtual 仍然只渲染视口内及其附近的元素,保持了虚拟化的性能优势。它会精确计算每个 lane 中的项目位置。

如何与 VirtualItem.lane 配合使用

当你设置了 lanes 选项后,virtualizer.getVirtualItems() 返回的每个 VirtualItem 对象都会多一个 lane 属性。这个 lane 属性是一个数字,表示当前项目被分配到了哪一个车道(索引从 0 开始)。

你需要利用这个 lane 属性来正确地定位你的渲染项目:

  • 计算项目的绝对位置: VirtualItem.start 告诉你项目在它所属的那个 lane 内部的起始偏移量(比如垂直布局中的 top)。但你还需要结合 VirtualItem.lane 和每个 lane 的宽度(或高度)来计算项目在整个容器中的实际 left(或 top)定位。
    例如,对于垂直布局的 3 列:
    • lane: 0 的项目可能 left: 0
    • lane: 1 的项目可能 left: columnWidth
    • lane: 2 的项目可能 left: 2 * columnWidth
  • 应用样式: 你可以根据 lane 属性为不同车道(列/行)的项目应用不同的样式,比如背景色、边框等。

示例场景

假设你想创建一个有 3 列的图片画廊,每张图片的高度都不同,并且希望图片能像瀑布一样向下排列填充空间:

  1. 配置 useVirtualizer:
    import { useVirtualizer } from '@tanstack/react-virtual';
    import React from 'react';
    
    function ImageGallery({ images }) {
      const parentRef = React.useRef();
      const columnCount = 3; // 设置为 3 列
    
      const virtualizer = useVirtualizer({
        count: images.length,
        getScrollElement: () => parentRef.current,
        estimateSize: index => images[index].estimatedHeight, // 估算图片高度
        lanes: columnCount, // 关键选项
      });
    
      const virtualItems = virtualizer.getVirtualItems();
      const totalSize = virtualizer.getTotalSize();
    
      // 假设你需要计算每列的宽度
      const columnWidth = 100 / columnCount; // 示例:每列占父容器宽度的百分比
    
  2. 渲染逻辑: 在渲染 virtualItems 时,你需要根据 virtualItem.lane 来计算其横向位置:
    return (
      <div ref={parentRef} style={{ height: '500px', overflow: 'auto', position: 'relative' }}>
        <div style={{ height: `${totalSize}px`, width: '100%', position: 'relative' }}>
          {virtualItems.map(virtualItem => (
            <img
              key={virtualItem.key}
              src={images[virtualItem.index].src}
              style={{
                position: 'absolute',
                top: 0,
                // 计算 translateX 来定位到正确的列
                transform: `translateX(${virtualItem.lane * columnWidth}%) translateY(${virtualItem.start}px)`,
                width: `${columnWidth}%`, // 每列的宽度
                height: `${virtualItem.size}px`, // 项目的实际高度
              }}
              // 如果需要动态测量,可以这样绑定 measureElement
              // ref={el => virtualizer.measureElement(el)}
              // data-index={virtualItem.index}
            />
          ))}
        </div>
      </div>
    );
    

总结

lanes 选项是 TanStack Virtual 专为 复杂多列/行布局(如瀑布流)设计的关键。它在逻辑层面将列表划分为并行车道,并提供 VirtualItem.lane 属性,让你可以结合 VirtualItem.startVirtualItem.size 精确地计算每个项目在多维空间中的最终定位,同时仍然享受虚拟化带来的高性能优势。