variable
<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 的内部逻辑,大大简化了这一过程:
- 自动布局与填充:
Virtualizer会尝试智能地将项目分配到各个lane中,目标是使每个lane的总高度(或宽度,取决于方向)保持相对平衡,从而避免出现某列/行过长而其他列/行为空的情况。这对于瀑布流布局尤其重要。 - 性能优化: 即使是多列/行布局,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 列的图片画廊,每张图片的高度都不同,并且希望图片能像瀑布一样向下排列填充空间:
- 配置
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; // 示例:每列占父容器宽度的百分比 - 渲染逻辑:
在渲染
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.start 和 VirtualItem.size 精确地计算每个项目在多维空间中的最终定位,同时仍然享受虚拟化带来的高性能优势。