Skip to content

Virtual Scroller Kit

v0.3.1UI-компонентыVue

Виртуальные список, таблица, грид, дерево и select для Vue 3 — динамические высоты строк, группировка с анимацией, sticky-заголовки, бесконечная прокрутка, поддержка RTL.

Virtual Scroller Kit
Начать знакомство →
npm install vue-virtual-scroller-kit@latest
01 — Назначение

Когда это пригодится

Обычный v-for создаёт DOM-узел на каждый элемент массива, и разница между 50 и 50 000 строк становится разницей между «просто работает» и «браузер вешает вкладку», а vue-virtual-scroller-kit держит в DOM только то, что реально видно на экране.

Таблица с данными растёт быстрее, чем помещается на экран

Тысячи строк, сортировка сразу по нескольким колонкам, закреплённые ряды и подгрузка по мере скролла — в браузере при этом отрисовывается только видимая часть таблицы, а не она целиком.

Длинный список разбит на секции

Категории товаров, дни в календаре, группы уведомлений — виртуализируются сразу и сами группы, и их содержимое, а заголовок текущей группы остаётся на виду, пока листаешь список внутри неё.

Порядок элементов нужно менять перетаскиванием

Плейлист, канбан-колонка, список приоритетных задач — элементы можно перетаскивать и переставлять с анимацией соседей и автопрокруткой, даже когда на экране видна лишь тысячная доля всего списка.

Выпадающий список выбирает из огромного справочника

Список стран, тикеров или пользователей на тысячи записей — сам выпадающий список остаётся быстрым за счёт виртуализации, а поиск с подгрузкой результатов заменяет отрисовку всех вариантов разом.

02 — Фичи

Коротко о главном

Виртуализация с динамическими высотами и motion blur

Виртуализация с динамическими высотами и motion blur

VirtualList и VirtualGrid рендерят только видимые элементы, поддерживая списки с миллионом записей. ResizeObserver автоматически измеряет реальную высоту каждой строки, а опция motion blur добавляет размытие, пропорциональное скорости прокрутки, для визуальной плавности. Поддерживаются горизонтальная и вертикальная прокрутка, внешний контейнер и оконный режим.

Группировка, анимации и sticky-заголовки

Группировка, анимации и sticky-заголовки

GroupedVirtualList отображает секции с коллапсируемыми группами и плавными CSS-анимациями раскрытия/схлопывания. Опция stickyGroupHeaders фиксирует заголовок текущей группы поверх списка. Встроенный VirtualScrollbar даёт кастомный скроллбар с изменяемыми размерами и цветами через CSS-переменные, а RTL-поддержка адаптирует макет автоматически.

Виртуальная таблица, сетка, дерево и select

Виртуальная таблица, сетка, дерево и select

VirtualTable с фиксированными колонками, сортировкой (в том числе мульти-), изменяемым размером и порядком колонок, закреплёнными рядами и бесконечной подгрузкой. VirtualTree поддерживает ленивую загрузку дочерних узлов с настраиваемым отступом. VirtualSelect виртуализирует выпадающий список с поиском, асинхронной загрузкой и кастомными слотами.

Композаблы для клавиатуры, перетаскивания и выделения

Композаблы для клавиатуры, перетаскивания и выделения

useVirtualKeyboardNav добавляет навигацию стрелками, Home/End, PageUp/Down с фокусом и активацией по Enter. useDraggableList реализует drag‑to‑reorder с анимированным смещением соседних элементов, призрачной копией и автоскроллом. useRowSelection управляет выбором строк с поддержкой Shift+клик, полным выделением и сбросом.

SSR-безопасность, производительность и тонкая настройка

SSR-безопасность, производительность и тонкая настройка

Компоненты безопасны для SSR: первые N строк рендерятся на сервере, а гидратация происходит без скачка. PositionManager с сегментным деревом обеспечивает O(log n) обновлений и запросов. Восстановление позиции прокрутки, anchor‑компенсация при изменении высоты строк, пул переиспользуемых DOM-узлов и кастомный скроллбар — всё для максимальной производительности.

Бесконечная подгрузка и отслеживание видимости

Бесконечная подгрузка и отслеживание видимости

Компонент InfiniteLoader подгружает следующую страницу данных незадолго до конца списка, скрывая задержку сети за плавным скроллом. Композабл useVisibilityTracker сообщает, какие элементы реально находятся в области просмотра — удобно для аналитики показов. Опция autoColWidths у VirtualTable сама подбирает ширину колонок по содержимому строк.

03 — Быстрый пример

Как это работает

Миллион строк — без просадки FPS

Рендерятся только видимые элементы плюс небольшой запас (overscan), высоты — динамические и переизмеряются на лету.

App.vue
<script setup lang="ts">
import { VirtualList } from 'vue-virtual-scroller-kit'

interface Row {
  id: number
  text: string
}

const items: Row[] = Array.from({ length: 100_000 }, (_, i) => ({
  id: i,
  text: `Row ${i + 1}`,
}))
</script>

<template>
  <VirtualList :items="items" :estimated-item-size="48" style="height: 600px">
    <template #default="{ item, index }">
      <div style="padding: 12px 16px; border-bottom: 1px solid #eee">
        {{ index + 1 }}. {{ item.text }}
      </div>
    </template>
  </VirtualList>
</template>

Группы со sticky-заголовками и сворачиванием

GroupedVirtualList виртуализирует список внутри секций — заголовок текущей группы остаётся закреплённым сверху, а toggle() сворачивает или разворачивает любую из них программно.

GroupedList.vue
<script setup lang="ts">
import { ref } from 'vue'
import { GroupedVirtualList } from 'vue-virtual-scroller-kit'
import type { GroupDef, GroupedVirtualListExpose } from 'vue-virtual-scroller-kit'

interface Contact {
  id: number
  name: string
  email: string
}

const groups = ref<GroupDef<Contact>[]>([
  {
    key: 'a',
    label: 'A',
    items: [
      { id: 1, name: 'Alice', email: 'alice@example.com' },
      { id: 2, name: 'Aaron', email: 'aaron@example.com' },
    ],
  },
  {
    key: 'b',
    label: 'B',
    items: [{ id: 3, name: 'Bob', email: 'bob@example.com' }],
    collapsed: true,
  },
])

const listRef = ref<GroupedVirtualListExpose | null>(null)
</script>

<template>
  <GroupedVirtualList
    ref="listRef"
    :groups="groups"
    :estimated-item-size="56"
    sticky-group-headers
    style="height: 500px"
  >
    <template #group-header="{ group, toggle, isCollapsed }">
      <div class="group-header" @click="toggle">
        {{ isCollapsed ? '▶' : '▼' }} {{ group.label }}
        <span>({{ group.items.length }})</span>
      </div>
    </template>

    <template #default="{ item }">
      <div class="contact-row">
        <strong>{{ item.name }}</strong>
        <span>{{ item.email }}</span>
      </div>
    </template>
  </GroupedVirtualList>
</template>

Перетаскивание для изменения порядка — без сторонней библиотеки

useDraggableList сам считает призрак-элемент под курсором, анимирует соседей и автоскроллит контейнер у края — остаётся только расставить getItemProps по вёрстке.

DraggableList.vue
<script setup lang="ts">
import { ref } from 'vue'
import { useDraggableList } from 'vue-virtual-scroller-kit'

interface Card {
  id: number
  label: string
}

const cards = ref<Card[]>(Array.from({ length: 50 }, (_, i) => ({ id: i, label: `Card ${i + 1}` })))
const listRef = ref<HTMLElement | null>(null)

const { isDragging, dragIndex, ghostStyle, getItemStyle, getItemProps } = useDraggableList({
  items: cards,
  scrollContainer: listRef,
  onReorder: (newItems) => {
    cards.value = newItems
  },
})
</script>

<template>
  <div ref="listRef" style="display: flex; flex-direction: column; gap: 6px; overflow-y: auto; height: 500px">
    <div
      v-for="(card, index) in cards"
      :key="card.id"
      v-bind="getItemProps(index)"
      :style="getItemStyle(index)"
      class="card"
    >
      ⣿ {{ card.label }}
    </div>
  </div>

  <Teleport to="body">
    <div v-if="isDragging && dragIndex >= 0" class="card card--ghost" :style="ghostStyle">
      ⣿ {{ cards[dragIndex]?.label }}
    </div>
  </Teleport>
</template>