Skip to content

VirtualList

Основной компонент. Рендерит только строки, видимые во вьюпорте, плюс буфер overscan. ResizeObserver измеряет каждую строку после монтирования, поэтому строки переменной высоты обрабатываются автоматически.

Пропы

ПропТипПо умолчаниюОписание
itemsT[]Массив данных
keyFieldstring'id'Поле, используемое как :key для каждой строки
estimatedItemSizenumber | (item, index) => number50Начальная оценка размера строки — высота, либо ширина при horizontal
overscannumber3Дополнительные строки, рендерящиеся над/под вьюпортом
minHeightnumber0Минимальная общая высота списка в px (вертикальный режим)
minWidthnumber0Минимальная общая ширина списка в px (режим horizontal)
scrollElementHTMLElement | nullnullВнешний контейнер прокрутки (взаимоисключим с pageMode)
pageModebooleanfalseИспользовать window как контейнер прокрутки. Фиксируется при монтировании — см. примечание ниже
horizontalbooleanfalseВиртуализация по scrollLeft/clientWidth вместо scrollTop/clientHeight. RTL-безопасно. Фиксируется при монтировании — см. примечание ниже
isLoadingbooleanfalseПоказывает слот #skeleton, когда items пуст
restoreKeystringКлюч для сохранения/восстановления позиции прокрутки в sessionStorage
ssrPreloadCountnumber20Число строк, рендерящихся на сервере
recyclePoolbooleanfalseПереиспользовать DOM-узлы вместо их размонтирования (лучше FPS прокрутки, отключает переходы на основе ключа)
motionBlurbooleanfalseПрименить 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Описание
scrollEventНативное событие прокрутки
visible-range-change{ start: number; end: number }Срабатывает при изменении видимого среза

Публичный API (VirtualListExpose)

ts
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'.

Примеры

Строки фиксированной высоты:

vue
<VirtualList :items="rows" :estimated-item-size="48" style="height: 500px">
  <template #default="{ item }">
    <div class="row">{{ item.name }}</div>
  </template>
</VirtualList>

Строки переменной высоты (оценка на элемент):

vue
<VirtualList
  :items="posts"
  :estimated-item-size="(item) => (item.isExpanded ? 200 : 60)"
  style="height: 600px"
>
  <template #default="{ item }">
    <PostCard :post="item" />
  </template>
</VirtualList>

Внешний контейнер прокрутки:

vue
<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-режим (прокручивается вся страница):

vue
<VirtualList :items="rows" page-mode :estimated-item-size="80">
  <template #default="{ item }"><Article :post="item" /></template>
</VirtualList>

Состояние skeleton-загрузки:

vue
<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>

Восстановление позиции прокрутки:

vue
<VirtualList :items="rows" restore-key="my-list" :estimated-item-size="48" style="height: 500px">
  <template #default="{ item }"><Row :data="item" /></template>
</VirtualList>

Пул переиспользования DOM (тяжёлые строки с высоким FPS):

vue
<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 при быстрой прокрутке:

vue
<VirtualList :items="rows" motion-blur :estimated-item-size="48" style="height: 500px">
  <template #default="{ item }">
    <div class="row">{{ item.name }}</div>
  </template>
</VirtualList>

Горизонтальная раскладка (лента карточек):

vue
<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"> для темизируемого скроллбара.