Virtual Scroll
A virtual scrolling component wrapped around @tanstack/vue-virtual, designed for high-performance scrolling scenarios with large data lists. Only elements within the visible area are rendered, significantly improving rendering performance for long lists.
Note: This component depends on
@tanstack/vue-virtualvia dynamic import as an optional dependency. If not installed, the component displays a prompt message instead of throwing an error. Install with:pnpm add @tanstack/vue-virtual
Demo
基础用法(5000 条数据)
斑马纹列表(striped)
带边框列表(bordered)
滚动到指定索引(scrollToIndex)
Installation
pnpm dlx brutx-vue@latest add virtual-scrollUsage
<script setup>
import { VirtualScroll } from 'brutx-ui-vue'
const items = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i + 1}`,
}))
</script>
<template>
<VirtualScroll :items="items" :item-height="48">
<template #default="{ item, index }">
<div class="p-4 border-b-2 border-brutal">
{{ item.name }}
</div>
</template>
</VirtualScroll>
</template>Striped List
<VirtualScroll
:items="items"
variant="striped"
size="lg"
>
<template #default="{ item }">
<div class="p-4">{{ item.name }}</div>
</template>
</VirtualScroll>Load More
<VirtualScroll
:items="items"
@scroll-end="loadMore"
>
<template #default="{ item }">
<div class="p-4">{{ item.name }}</div>
</template>
<template #loading>
<div class="p-4 text-center">Loading...</div>
</template>
</VirtualScroll>Empty State
<VirtualScroll :items="[]">
<template #empty>
<div class="p-8 text-center">
<Icon name="empty" class="w-16 h-16 mx-auto mb-4" />
<p>No data available</p>
</div>
</template>
</VirtualScroll>Bordered List
<VirtualScroll
:items="items"
variant="bordered"
:item-height="64"
>
<template #default="{ item }">
<div class="flex items-center gap-4 p-4">
<Avatar :src="item.avatar" />
<div>
<p class="font-bold">{{ item.name }}</p>
<p class="text-sm text-muted">{{ item.email }}</p>
</div>
</div>
</template>
</VirtualScroll>Variants
| Variant | Description |
|---|---|
default | Default style |
striped | Zebra striping (even row background color) |
bordered | Bottom border |
Sizes
| Size | Max Height |
|---|---|
sm | max-h-64 (16rem) |
default | max-h-96 (24rem) |
lg | max-h-[32rem] |
xl | max-h-[48rem] |
full | max-h-full (requires parent container to have a definite height, e.g. h-screen/h-96) |
Data Types
interface VirtualScrollItem {
id?: string | number
[key: string]: unknown
}Programmatic Control
After referencing the component via ref, you can call the following methods (exposed via defineExpose):
<script setup>
import { ref } from 'vue'
import { VirtualScroll } from 'brutx-ui-vue'
const listRef = ref(null)
const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i + 1}` }))
function jumpToMiddle() {
listRef.value?.scrollToIndex(Math.floor(items.length / 2))
}
</script>
<template>
<Button @click="jumpToMiddle">Jump to Middle</Button>
<VirtualScroll ref="listRef" :items="items" :item-height="48">
<template #default="{ item }">
<div class="p-4">{{ item.name }}</div>
</template>
</VirtualScroll>
</template>API Reference
| Name | Type | Default | Description |
|---|---|---|---|
class | string | undefined | undefined | Custom CSS classes appended to the virtual scroll root container. |
DetailsDeclared typestring | undefined Default value details
Sourcepackages/ui/src/components/virtual-scroll/types.ts | |||
itemsRequired | T[] | — | The data array to virtualize. Its length determines the item count in the virtualizer. |
DetailsDeclared typeT[] Default value details
Sourcepackages/ui/src/components/virtual-scroll/types.ts | |||
itemHeight | number | undefined | 48 | The estimated item height in pixels for fixed-height mode; dynamic-height mode also uses it as the initial estimate. |
DetailsDeclared typenumber | undefined Default value details
Sourcepackages/ui/src/components/virtual-scroll/types.ts | |||
dynamicHeight | boolean | undefined | false | Enables measurement of mounted items’ actual heights. When false, each item uses the fixed itemHeight. |
DetailsDeclared typeboolean | undefined Literal values
Default value details
Sourcepackages/ui/src/components/virtual-scroll/types.ts | |||
size | "default" | "lg" | "sm" | "xl" | "full" | undefined | "default" | The container size variant. The full variant requires a parent with a definite height. |
DetailsDeclared type"default" | "lg" | "sm" | "xl" | "full" | undefined Literal values
Default value details
Sourcepackages/ui/src/components/virtual-scroll/types.ts | |||
variant | "default" | "bordered" | "striped" | undefined | "default" | Selects the item style. Striped adds an alternating background to odd zero-based indices; bordered adds borders. |
DetailsDeclared type"default" | "bordered" | "striped" | undefined Literal values
Default value details
Sourcepackages/ui/src/components/virtual-scroll/types.ts | |||
overscan | number | undefined | 5 | The number of extra items rendered beyond the visible region, passed to the virtualizer. |
DetailsDeclared typenumber | undefined Default value details
Sourcepackages/ui/src/components/virtual-scroll/types.ts | |||
scrollEndThreshold | number | undefined | 50 | The pixel distance from the bottom at which scroll-end is emitted. |
DetailsDeclared typenumber | undefined Default value details
Sourcepackages/ui/src/components/virtual-scroll/types.ts | |||
role | string | undefined | "list" | Sets the ARIA role on the virtual list scroll container; defaults to list. |
DetailsDeclared typestring | undefined Default value details
Sourcepackages/ui/src/components/virtual-scroll/types.ts | |||
itemRole | string | undefined | "listitem" | Sets the ARIA role on each virtual list item; defaults to listitem. |
DetailsDeclared typestring | undefined Default value details
Sourcepackages/ui/src/components/virtual-scroll/types.ts | |||
| Name | Signature or type | Description |
|---|---|---|
@scroll-end | [] | Emitted when scrolling enters the bottom range set by scrollEndThreshold. It can fire again after leaving and re-entering that range. |
DetailsDeclared type[] Sourcepackages/ui/src/components/virtual-scroll/VirtualScroll.vue | ||
@scroll | [scrollTop: number] | Emitted with the current scrollTop in pixels when the scroll container scrolls. |
DetailsDeclared type[scrollTop: number] Sourcepackages/ui/src/components/virtual-scroll/VirtualScroll.vue | ||
| Name | Signature or type | Description |
|---|---|---|
#loading | {} | Provides loading content. It renders while the virtualizer dependency loads and at the bottom of a ready, non-empty list. |
DetailsDeclared type{}Sourcepackages/ui/src/components/virtual-scroll/VirtualScroll.vue | ||
#empty | {} | Replaces the empty-state message and is rendered only in the empty-list branch.
|
DetailsDeclared type{}Sourcepackages/ui/src/components/virtual-scroll/VirtualScroll.vue | ||
#default | { item: T; index: number; } | Renders each visible virtual item. Its scope provides the original item and its zero-based index. |
DetailsDeclared type{ item: T; index: number; }Sourcepackages/ui/src/components/virtual-scroll/VirtualScroll.vue | ||
| Name | Signature or type | Description |
|---|---|---|
scrollToIndex | (index: number) => void | Scrolls to an item index. The index is clamped to the available range; it does nothing for an empty list or before the virtualizer is available. |
DetailsDeclared type(index: number) => void Sourcepackages/ui/src/components/virtual-scroll/VirtualScroll.vue | ||
measureElement | (el: Element | null) => void | Passes the specified DOM element to the virtualizer for measurement. It does nothing for null or before the virtualizer is ready. |
DetailsDeclared type(el: Element | null) => void Sourcepackages/ui/src/components/virtual-scroll/VirtualScroll.vue | ||
measure | () => void | Re-measures all mounted item sizes, for use after content heights change. |
DetailsDeclared type() => void Sourcepackages/ui/src/components/virtual-scroll/VirtualScroll.vue | ||
virtualizer | ShallowRef<VirtualizerInstance | null, VirtualizerInstance | null> | The shallow reactive reference to the underlying TanStack Virtualizer. Its value is null until the dependency loads, and remains null if loading fails. |
DetailsDeclared typeShallowRef<VirtualizerInstance | null, VirtualizerInstance | null> Literal values
Referenced types VirtualizerInstance Type definition — VirtualizerInstanceexport interface VirtualizerInstance {
getVirtualItems: () => VirtualizerVirtualItem[]
getTotalSize: () => number
scrollToIndex: (index: number) => void
measure: () => void
measureElement: (el: Element | null) => void
}Sourcepackages/ui/src/components/virtual-scroll/VirtualScroll.vue | ||
Accessibility
- ARIA Attributes: Uses
role="list"androle="listitem"for semantic markup; supportsaria-setsizeandaria-posinsetattributes - Keyboard: Supports
aria-labelfor internationalized labels
FAQ
Q: What should I do when list items have variable heights and the virtual scroll jumps around?
A: itemHeight is an estimated value used to calculate scroll position. If list items vary significantly in height, it may cause jumping during scrolling. It is recommended to set itemHeight to the average height of most list items, or ensure consistent heights via CSS. Increasing the overscan value can also help reduce the jumping effect.
Q: The component shows a prompt message instead of rendering normally after installation. Why?
A: VirtualScroll depends on @tanstack/vue-virtual, which is an optional dependency. If the package is not installed, the component displays an installation prompt instead of throwing an error. Run pnpm add @tanstack/vue-virtual to install the dependency and restart your project.
Q: The scroll-end event is not firing when I scroll to the bottom?
A: Please check whether the data source is provided correctly. The scroll-end event only fires when the container actually scrolls and reaches the bottom. If the data volume is insufficient to produce a scrollbar (the list height does not exceed the container), the event will not fire. You can adjust the scrollEndThreshold value to control the trigger sensitivity.