VirtualList
Основной компонент. Рендерит только строки, видимые во вьюпорте, плюс буфер overscan. ResizeObserver измеряет каждую строку после монтирования, поэтому строки переменной высоты обрабатываются автоматически.
Пропы
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
items | T[] | — | Массив данных |
keyField | string | 'id' | Поле, используемое как :key для каждой строки |
estimatedItemSize | number | (item, index) => number | 50 | Начальная оценка размера строки — высота, либо ширина при horizontal |
overscan | number | 3 | Дополнительные строки, рендерящиеся над/под вьюпортом |
minHeight | number | 0 | Минимальная общая высота списка в px (вертикальный режим) |
minWidth | number | 0 | Минимальная общая ширина списка в px (режим horizontal) |
scrollElement | HTMLElement | null | null | Внешний контейнер прокрутки (взаимоисключим с pageMode) |
pageMode | boolean | false | Использовать window как контейнер прокрутки. Фиксируется при монтировании — см. примечание ниже |
horizontal | boolean | false | Виртуализация по scrollLeft/clientWidth вместо scrollTop/clientHeight. RTL-безопасно. Фиксируется при монтировании — см. примечание ниже |
isLoading | boolean | false | Показывает слот #skeleton, когда items пуст |
restoreKey | string | — | Ключ для сохранения/восстановления позиции прокрутки в sessionStorage |
ssrPreloadCount | number | 20 | Число строк, рендерящихся на сервере |
recyclePool | boolean | false | Переиспользовать DOM-узлы вместо их размонтирования (лучше FPS прокрутки, отключает переходы на основе ключа) |
motionBlur | boolean | false | Применить CSS-размытие, масштабируемое по скорости прокрутки, снимаемое ~150мс после остановки |
pageModeиhorizontalсчитываются один раз при монтировании — как выбор оси/режима, а не реактивный проп в реальном времени. Если ваш UI позволяет пользователям переключатьhorizontalво время выполнения, привяжите:keyк значению, которое им управляет (например,:key="layout"), чтобы Vue перемонтировал компонент вместо того, чтобы оставить прежнюю ось подключённой — см. переключатель Layout на вкладке демо VirtualList для рабочего примера.
Слоты
| Слот | Область видимости | Описание |
|---|---|---|
#default | { item: T, index: number, style } | Содержимое строки |
#empty | — | Рендерится, когда items пуст и загрузки нет |
#skeleton | — | Рендерится, когда items пуст и isLoading истинно |
#loading | — | Рендерится внизу, пока isLoading истинно |
Emits
| Событие | Payload | Описание |
|---|---|---|
scroll | Event | Нативное событие прокрутки |
visible-range-change | { start: number; end: number } | Срабатывает при изменении видимого среза |
Публичный API (VirtualListExpose)
import type { VirtualListExpose } from 'vue-virtual-scroller-kit'
const listRef = ref<VirtualListExpose | null>(null)
listRef.value?.scrollTo(index, align) // 'start' | 'center' | 'end' | 'auto'
listRef.value?.scrollTo(index, 'start', { behavior: 'smooth' }) // анимация нативного плавного скролла
listRef.value?.scrollToOffset(px) // прокрутить к пиксельному смещению
listRef.value?.scrollToOffset(px, { behavior: 'smooth' })
listRef.value?.measureItem(index, height) // вручную задать высоту строки
listRef.value?.getScrollElement() // элемент, который реально прокручивается — сочетайте с VirtualScrollbar
behavior: 'smooth'по умолчанию'auto'(мгновенный прыжок, как и раньше). Плавная прокрутка к далёкому виртуализированному индексу нацеливается на текущее оценочное смещение. Прямая записьscrollTop(например, из компенсации якоря, описанной ниже) иначе отменила бы выполняющийся нативный плавный скролл, поэтому эта компенсация подавляется ~1с после любого вызова сbehavior: 'smooth'.
Примеры
Строки фиксированной высоты:
<VirtualList :items="rows" :estimated-item-size="48" style="height: 500px">
<template #default="{ item }">
<div class="row">{{ item.name }}</div>
</template>
</VirtualList>Строки переменной высоты (оценка на элемент):
<VirtualList
:items="posts"
:estimated-item-size="(item) => (item.isExpanded ? 200 : 60)"
style="height: 600px"
>
<template #default="{ item }">
<PostCard :post="item" />
</template>
</VirtualList>Внешний контейнер прокрутки:
<div ref="scrollEl" style="overflow-y: auto; height: 400px">
<VirtualList :items="rows" :scroll-element="scrollEl" :estimated-item-size="48">
<template #default="{ item }"><Row :data="item" /></template>
</VirtualList>
</div>Page-режим (прокручивается вся страница):
<VirtualList :items="rows" page-mode :estimated-item-size="80">
<template #default="{ item }"><Article :post="item" /></template>
</VirtualList>Состояние skeleton-загрузки:
<VirtualList :items="items" :is-loading="isLoading" :estimated-item-size="56" style="height: 500px">
<template #default="{ item }"><Item :data="item" /></template>
<template #skeleton>
<SkeletonRow v-for="i in 10" :key="i" />
</template>
<template #empty>
<div>No items found.</div>
</template>
</VirtualList>Восстановление позиции прокрутки:
<VirtualList :items="rows" restore-key="my-list" :estimated-item-size="48" style="height: 500px">
<template #default="{ item }"><Row :data="item" /></template>
</VirtualList>Пул переиспользования DOM (тяжёлые строки с высоким FPS):
<VirtualList :items="rows" recycle-pool :estimated-item-size="80" style="height: 500px">
<template #default="{ item }"><HeavyRow :data="item" /></template>
</VirtualList>Примечание:
recyclePoolпереиспользует DOM-узлы, поэтому переходы Vue на основе ключей для отдельных элементов работать не будут. Используйте, когда производительность рендера важнее покадровых анимаций элементов.
Motion blur при быстрой прокрутке:
<VirtualList :items="rows" motion-blur :estimated-item-size="48" style="height: 500px">
<template #default="{ item }">
<div class="row">{{ item.name }}</div>
</template>
</VirtualList>Горизонтальная раскладка (лента карточек):
<VirtualList horizontal :items="cards" :estimated-item-size="220" style="height: 240px">
<template #default="{ item }">
<div class="card" style="width: 220px; height: 100%">{{ item.title }}</div>
</template>
</VirtualList>В горизонтальном режиме
estimatedItemSize— это оценка ширины, и каждая строка позиционируется черезinset-inline-start(RTL-безопасно) сheight: 100%— содержимое слота само отвечает за собственнуюwidth(фиксированную либо измеряемую динамически черезResizeObserver, как и высоты строк в вертикальном режиме). Сочетайте с<VirtualScrollbar orientation="horizontal">для темизируемого скроллбара.