@glhrmoura/vue-virtualize
v0.2.2
Published
Headless virtualization for Vue 3 lists, grids, and tables
Maintainers
Readme
Vue Virtualize
Headless virtualization for Vue 3 lists, grids, and tables.
Documentation
Install
yarn add @glhrmoura/vue-virtualizenpm install @glhrmoura/vue-virtualizeVue 3.5+ is a peer dependency.
Usage
VirtualList
<script setup lang="ts">
import { VirtualList } from '@glhrmoura/vue-virtualize'
import { shallowRef } from 'vue'
const users = shallowRef(
Array.from({ length: 100_000 }, (_, index) => ({
id: index + 1,
name: `User ${index + 1}`,
})),
)
</script>
<template>
<VirtualList :items="users" :height="500" :get-item-key="(user) => user.id">
<template #default="{ item, index }">
<UserCard :user="item" :index="index" />
</template>
</VirtualList>
</template>estimateSize defaults to 48. A number locks a fixed item size (count × size, best for huge lists). Pass a function when rows have variable height so the list can measure mounted elements without clipping. Container scroll needs a height (unless window-scroll).
Horizontal list
<VirtualList
:items="slides"
horizontal
:width="640"
:height="180"
:estimate-size="200"
:get-item-key="(slide) => slide.id"
>
<template #default="{ item }">
<SlideCard :slide="item" />
</template>
</VirtualList>Window scroll
Use the document/window as the scroller. No fixed container height required.
<VirtualList
:items="posts"
window-scroll
:estimate-size="72"
:get-item-key="(post) => post.id"
@visible-range-change="onRange"
@scroll-end="onScrollEnd"
>
<template #default="{ item }">
<PostCard :post="item" />
</template>
</VirtualList>Reverse / chat feed
reverse starts at the end, sticks to the bottom when new items append, and keeps scroll stable when older items are prepended (requires stable get-item-key).
<VirtualList
:items="messages"
reverse
:height="480"
:estimate-size="56"
:get-item-key="(message) => message.id"
@visible-range-change="maybeLoadOlder"
>
<template #default="{ item }">
<MessageBubble :message="item" />
</template>
</VirtualList>VirtualGrid
<VirtualGrid
:items="photos"
:column-count="4"
:estimate-row-size="120"
:estimate-column-size="180"
:height="500"
>
<template #default="{ item, rowIndex, columnIndex }">
<PhotoCard :photo="item" :row="rowIndex" :column="columnIndex" />
</template>
</VirtualGrid>VirtualTable
Sticky header is on by default. Pin columns with pin: 'start' | 'end' (first column, actions, etc.). Give pinned cells a solid background so content does not show through.
const columns = [
{ id: 'name', header: 'Name', width: 220, pin: 'start' },
{ id: 'email', header: 'Email', width: 280 },
{ id: 'actions', header: '', width: 120, pin: 'end' },
]<VirtualTable :rows="users" :columns="columns" :estimate-row-size="44" :height="500">
<template #cell="{ row, column, value }">
<span>{{ value }}</span>
</template>
</VirtualTable>useVirtualizer
const { virtualItems, totalSize, scrollToIndex } = useVirtualizer({
count: items.length,
container,
estimateSize: () => 52,
overscan: 4,
windowScroll: false,
reverse: false,
onVisibleRangeChange: (range) => {
if (range.endIndex >= range.count - 8) loadMore()
},
onScrollEnd: () => {},
})Events / callbacks
| Name | Where | When |
| --- | --- | --- |
| visibleRangeChange / onVisibleRangeChange | components / composables | Visible index window changes |
| scrollEnd / onScrollEnd | components / composables | Scroll settles (~120ms idle) |
API Reference
| Export | Role |
| --- | --- |
| VirtualList | One axis, vertical or horizontal, optional window-scroll / reverse |
| VirtualGrid | Flat array, columnCount, row and column estimates |
| VirtualTable | rows, columns, sticky header, pinned columns |
| useVirtualizer | Custom markup driven by virtualItems and totalSize |
| useVirtualList | Headless list adapter |
| useVirtualGrid | Headless grid adapter |
| useVirtualTable | Headless table adapter |
License
Copyright (c) Guilherme Moura
