Skip to content

GroupedVirtualList ​

Рендерит элементы, сгруппированные под сворачиваемыми заголовками секций. Каждая группа может раскрываться или сворачиваться с плавной CSS-анимацией.

Пропы ​

groups ​

GroupDef<T>[]

Массив определений групп.

estimatedItemSize ​

number · по умолчанию: 50

Оценочная высота строк элементов.

estimatedGroupHeaderSize ​

number · по умолчанию: 40

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

overscan ​

number · по умолчанию: 3

Дополнительные строки, рендерящиеся вне вьюпорта.

keyField ​

string · по умолчанию: 'id'

Поле, используемое как ключ элемента.

motionBlur ​

boolean · по умолчанию: false

Применить CSS-размытие, масштабируемое по скорости прокрутки при быстрой прокрутке.

stickyGroupHeaders ​

boolean · по умолчанию: false

Держать заголовок текущей группы прикреплённым сверху, пока её элементы прокручиваются под ним (оверлей, см. ниже).

stickyGroupHeaders рендерится как постоянный оверлей над списком, переиспользуя слот #group-header для той группы, что сейчас находится вверху вьюпорта — это не настоящая CSS-строка position: sticky, что здесь невозможно, поскольку виртуализированные строки — position: absolute. Он мгновенно перескакивает к следующей группе (без анимации «выталкивания»).

Определение группы ​

GroupDef<T> — форма каждой записи в пропе groups.

ts
interface GroupDef<T> {
  key: string // уникальный идентификатор группы
  label: string // отображаемая метка
  items: T[] // элементы в этой группе
  collapsed?: boolean // начальное состояние свёрнутости
}

Слоты ​

group-header ​

Область видимости: { group: GroupDef<T>, toggle: () => void, isCollapsed: boolean }

Кастомный заголовок группы.

default ​

Область видимости: { item: T, index: number, groupKey: string }

Содержимое строки элемента.

empty ​

Область видимости: нет

Показывается, когда все группы пусты.

Emits ​

Те же, что у VirtualList: scroll, visible-range-change.

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

toggle(groupKey) ​

Раскрыть/свернуть группу.

scrollTo(index, align?, options?) ​

Прокрутить к плоскому индексу строки.

getScrollElement() ​

Возвращает элемент, который реально прокручивается — сочетайте с VirtualScrollbar.

ts
import type { GroupedVirtualListExpose } from 'vue-virtual-scroller-kit'

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

listRef.value?.toggle('group-key') // раскрыть/свернуть группу
listRef.value?.scrollTo(index) // прокрутить к плоскому индексу строки
listRef.value?.scrollTo(index, 'start', { behavior: 'smooth' })
listRef.value?.getScrollElement() // сочетайте с VirtualScrollbar

Пример ​

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)

function expandAll() {
  groups.value = groups.value.map((g) => ({ ...g, collapsed: false }))
}
function collapseAll() {
  groups.value = groups.value.map((g) => ({ ...g, collapsed: true }))
}
</script>

<template>
  <button @click="expandAll">Expand all</button>
  <button @click="collapseAll">Collapse all</button>

  <GroupedVirtualList
    ref="listRef"
    :groups="groups"
    :estimated-item-size="56"
    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>