Vitral 0.2
Data

VirtualScroller

Renders only the items near the viewport, so a hundred thousand rows cost what a screenful does, with lazy loading.

Import

main.ts
import { VirtualScroller } from '@vitral/vue';

A hundred thousand rows

Item #0
Item #1
Item #2
Item #3
Item #4
Item #5
Item #6
Item #7
Item #8
Item #9
Item #10
<script setup lang="ts">import { Button, VirtualScroller } from '@vitral/vue';import { ref } from 'vue'; const items = Array.from({ length: 100000 }, (_, i) => ({ id: i, name: `Item #${i}` })); const scroller = ref<InstanceType<typeof VirtualScroller> | null>(null);</script> <template>    <Button label="Jump to #50,000" severity="secondary" style="align-self: flex-start" @click="scroller?.scrollToIndex(50000)" />    <VirtualScroller ref="scroller" :items="items" :item-size="40" scroll-height="16rem" data-key="id" aria-label="Items" style="border: 1px solid var(--vt-content-border-color)">        <template #item="{ item, options }">            <div :style="{ height: '40px', display: 'flex', alignItems: 'center', padding: '0 1rem', background: options.odd ? 'var(--vt-content-hover-background)' : undefined }">                {{ (item as { name: string }).name }}            </div>        </template>    </VirtualScroller></template>

Horizontal

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup lang="ts">import { VirtualScroller } from '@vitral/vue'; const columns = Array.from({ length: 1000 }, (_, i) => i);</script> <template>    <VirtualScroller :items="columns" :item-size="72" orientation="horizontal" scroll-width="100%" style="height: 5rem; border: 1px solid var(--vt-content-border-color)" aria-label="Columns">        <template #item="{ item }">            <div style="display: flex; align-items: center; justify-content: center; height: 100%; border-inline-end: 1px solid var(--vt-content-border-color)">{{ item }}</div>        </template>    </VirtualScroller></template>

Lazy

Row 0…
Row 1…
Row 2…
Row 3…
Row 4…
Row 5…
Row 6…
Row 7…
Row 8…
Row 9…
Row 10…
Loading…
<script setup lang="ts">import { VirtualScroller } from '@vitral/vue';import { ref } from 'vue'; const lazyItems = ref<(string | null)[]>(Array.from({ length: 5000 }, () => null));const loading = ref(false);function load({ first, last }: { first: number; last: number }) {    loading.value = true;    setTimeout(() => {        const next = [...lazyItems.value];        for (let i = first; i < last; i++) next[i] = `Loaded row ${i}`;        lazyItems.value = next;        loading.value = false;    }, 400);}</script> <template>    <VirtualScroller :items="lazyItems" :item-size="36" scroll-height="14rem" lazy :loading="loading" :delay="150" aria-label="Lazy rows" style="width: 100%; border: 1px solid var(--vt-content-border-color)" @lazy-load="load">        <template #item="{ item, options }">            <div style="height: 36px; display: flex; align-items: center; padding: 0 1rem">{{ item ?? `Row ${options.index}…` }}</div>        </template>    </VirtualScroller></template>

API

Read from packages/vue/src/components/VirtualScroller/types.ts, so it says what the component actually accepts.

Props

NameTypeDescription
itemsany[]Every item; only the ones near the viewport are rendered.
itemSize *numberOne item's height (or width, horizontally) in pixels.
orientation'vertical' | 'horizontal'Scroll along `'vertical'` (the default) or `'horizontal'`.
scrollHeightstringThe container's height (or width), when its parent does not set one.
scrollWidthstring—
numToleratedItemsnumberItems rendered past each edge of the viewport. Defaults to half a viewport.
lazybooleanAsk for items as they come into view (`lazy-load`) instead of having them all.
loadingbooleanShow the loader over the list.
disabledbooleanRender every item, as a plain container would.
delaynumberMilliseconds to wait after scrolling stops before `lazy-load`. Defaults to 0.
dataKeystringA key field for the rendered items; their index is used without one.

Plus pt, dt and unstyled from BaseProps, see pass-through and unstyled mode.

Emits

EventPayloadDescription
scroll-index-changeevent: { first: number; last: number }—
lazy-loadevent: { first: number; last: number }In lazy mode, the range of items the scroller is about to show.
scrollevent: Event—

Slots

NameSlot propsDescription
item(props: { item: unknown; options: VirtualScrollerItemOptions })—
content(props: { items: unknown[]; first: number; last: number; styleOffset: Record<string, string>; itemSize: number })Replaces the rendered range entirely, for a table body, say. `styleOffset` places it.
loader——