VirtualGrid & VirtualTree
VirtualGrid
Виртуальный грид, располагающий элементы по строкам и столбцам. Число столбцов может быть фиксированным либо вычисляться автоматически из columnWidth и ширины контейнера.
Пропы
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
items | T[] | — | Массив данных |
columns | number | 0 | Фиксированное число столбцов. Передайте 0 для автовычисления из columnWidth |
columnWidth | number | 200 | Ширина ячейки для автовычисления столбцов |
rowHeight | number | 200 | Высота ячейки в px — начальная оценка при включённом dynamicRowHeight |
gap | number | 8 | Промежуток между ячейками в px |
keyField | string | 'id' | Поле ключа |
overscan | number | 2 | Дополнительные строки, рендерящиеся вне вьюпорта |
isLoading | boolean | false | Показывает skeleton, когда items пуст |
motionBlur | boolean | false | Применить CSS-размытие, масштабируемое по скорости прокрутки при быстрой прокрутке |
dynamicRowHeight | boolean | false | Измерять реальную высоту каждой строки через ResizeObserver вместо фиксированной rowHeight (высота строки = максимум по ячейкам этой строки) |
Слоты
| Слот | Область видимости | Описание |
|---|---|---|
#default | { item: T, index: number, row: number, col: number } | Содержимое ячейки |
#empty | — | Показывается, когда items пуст |
#skeleton | — | Показывается при пустом списке и isLoading |
Emits
scroll, visible-range-change.
Пример
vue
<script setup lang="ts">
import { VirtualGrid } from 'vue-virtual-scroller-kit'
interface Photo {
id: number
url: string
title: string
}
const photos: Photo[] = Array.from({ length: 10_000 }, (_, i) => ({
id: i,
url: `https://picsum.photos/seed/${i}/200/200`,
title: `Photo ${i + 1}`,
}))
</script>
<template>
<VirtualGrid
:items="photos"
:column-width="220"
:row-height="220"
:gap="12"
style="height: 600px"
>
<template #default="{ item }">
<div class="photo-card">
<img :src="item.url" :alt="item.title" />
<p>{{ item.title }}</p>
</div>
</template>
</VirtualGrid>
</template>Динамическая высота строки — когда высота содержимого ячеек различается (например, подписи разной длины), высота строки измеряется по каждой строке, а не фиксируется:
vue
<VirtualGrid
:items="photos"
:column-width="220"
:row-height="220"
dynamic-row-height
:gap="12"
style="height: 600px"
>
<template #default="{ item }">
<div class="photo-card" style="height: auto">
<img :src="item.url" :alt="item.title" />
<p>{{ item.title }}</p>
<p v-if="item.caption" class="photo-card__caption">{{ item.caption }}</p>
</div>
</template>
</VirtualGrid>При
dynamicRowHeightячейки больше не получают фиксированнуюheightот грида — задайте имheight: auto(либо оставьтеheightнезаданным), чтобы их содержимое определяло реальную высоту строки.
VirtualTree
Дерево с раскрытием/сворачиванием, настраиваемым отступом на уровень вложенности и опциональной ленивой (асинхронной) загрузкой дочерних элементов.
Пропы
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
nodes | TreeNode<T>[] | — | Корневые узлы |
indent | number | 20 | Отступ в px на уровень вложенности |
estimatedItemSize | number | 36 | Оценочная высота строки |
overscan | number | 5 | Дополнительные строки, рендерящиеся вне вьюпорта |
onLoadChildren | (node) => Promise<TreeNode<T>[]> | — | Вызывается при первом раскрытии узла с hasChildren: true |
motionBlur | boolean | false | Применить CSS-размытие, масштабируемое по скорости прокрутки при быстрой прокрутке |
TreeNode<T>
ts
interface TreeNode<T extends Record<string, unknown> = Record<string, unknown>> {
id: string | number
data: T
children?: TreeNode<T>[]
hasChildren?: boolean // true = есть дочерние элементы, которые ещё не загружены
}FlatTreeRow<T>
Область видимости слота содержит:
ts
interface FlatTreeRow<T> {
node: TreeNode<T>
depth: number
isExpanded: boolean
hasChildren: boolean
isLoading: boolean // true, пока выполняется onLoadChildren
}Слоты
| Слот | Область видимости | Описание |
|---|---|---|
#default | { row: FlatTreeRow<T>, index: number } | Кастомное содержимое строки. Кнопка раскрытия рендерится самим компонентом |
#empty | — | Пустое состояние |
Emits
| Событие | Payload |
|---|---|
node-expand | TreeNode<T> |
node-collapse | TreeNode<T> |
node-click | [TreeNode<T>, depth: number] |
Публичный API
ts
treeRef.value?.expandAll()
treeRef.value?.collapseAll()
treeRef.value?.scrollTo(index)
treeRef.value?.expandedIds // Readonly<Ref<Set<string | number>>>Пример
vue
<script setup lang="ts">
import { VirtualTree } from 'vue-virtual-scroller-kit'
import type { TreeNode, FlatTreeRow } from 'vue-virtual-scroller-kit'
interface FileNode {
name: string
type: 'file' | 'folder'
}
const nodes: TreeNode<FileNode>[] = [
{
id: 1,
data: { name: 'src', type: 'folder' },
children: [
{ id: 2, data: { name: 'main.ts', type: 'file' } },
{ id: 3, data: { name: 'App.vue', type: 'file' } },
],
},
{
id: 4,
data: { name: 'node_modules', type: 'folder' },
hasChildren: true, // ленивая загрузка
},
]
async function loadChildren(node: TreeNode<FileNode>): Promise<TreeNode<FileNode>[]> {
const res = await fetch(`/api/children/${node.id}`)
return res.json()
}
</script>
<template>
<VirtualTree
:nodes="nodes"
:indent="20"
:on-load-children="loadChildren"
style="height: 400px"
@node-click="(node, depth) => console.log(node.data.name, depth)"
>
<template #default="{ row }">
<span>{{ row.node.data.type === 'folder' ? '📁' : '📄' }} {{ row.node.data.name }}</span>
</template>
</VirtualTree>
</template>