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-безопасно. Фиксируется при монтировании — см. примечание ниже.
pageModeиhorizontalсчитываются один раз при монтировании — как выбор оси/режима, а не реактивный проп в реальном времени. Если ваш UI позволяет пользователям переключатьhorizontalво время выполнения, привяжите:keyк значению, которое им управляет (например,:key="layout"), чтобы Vue перемонтировал компонент вместо того, чтобы оставить прежнюю ось подключённой — см. переключатель Layout на вкладке демо VirtualList для рабочего примера.
isLoading
boolean · по умолчанию: false
Показывает слот #skeleton, когда items пуст.
restoreKey
string · по умолчанию: —
Ключ для сохранения/восстановления позиции прокрутки в sessionStorage.
ssrPreloadCount
number · по умолчанию: 20
Число строк, рендерящихся на сервере.
recyclePool
boolean · по умолчанию: false
Переиспользовать DOM-узлы вместо их размонтирования (лучше FPS прокрутки, отключает переходы на основе ключа).
motionBlur
boolean · по умолчанию: false
Применить CSS-размытие, масштабируемое по скорости прокрутки, снимаемое ~150мс после остановки.
containerRole
string · по умолчанию: 'list'
ARIA-роль контейнера прокрутки. Оборачивающий компонент (например, VirtualTree или VirtualSelect) выставляет 'none', когда роль уже задана снаружи — тогда элемент убирается из дерева доступности вместо вложения одной роли в другую.
itemRole
string · по умолчанию: 'listitem'
ARIA-роль обёртки каждой строки. Установите 'none', если содержимое слота само рендерит реальную роль строки (например, treeitem, option) — иначе обёртка создаст невалидное вложенное дерево доступности.
Слоты
default
Область видимости: { item: T, index: number, style }
Содержимое строки.
empty
Область видимости: нет
Рендерится, когда items пуст и загрузки нет.
skeleton
Область видимости: нет
Рендерится, когда items пуст и isLoading истинно.
loading
Область видимости: нет
Рендерится внизу, пока isLoading истинно.
Emits
scroll
Payload: Event
Нативное событие прокрутки.
visible-range-change
Payload: { start: number; end: number }
Срабатывает при изменении видимого среза.
Публичный API (VirtualListExpose)
scrollTo(index, align?, options?)
Прокрутить к элементу. align — 'start' | 'center' | 'end' | 'auto'. options.behavior — 'auto' (по умолчанию, мгновенно) или 'smooth' (нативная анимация плавного скролла).
scrollToOffset(px, options?)
Прокрутить к пиксельному смещению. Тот же options.behavior.
measureItem(index, height)
Вручную задать высоту строки.
getScrollElement()
Возвращает элемент, который реально прокручивается — сочетайте с VirtualScrollbar.
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">для темизируемого скроллбара.