Перетаскивание для сортировки
useDraggableList() — composable для drag-to-reorder на pointer-событиях. Показывает призрачный элемент с фиксированным позиционированием, следующий за курсором, анимирует соседние элементы через translateY и автоматически прокручивает контейнер при перетаскивании у краёв.
function useDraggableList<T>(options: UseDraggableListOptions<T>): UseDraggableListReturnОпции
items
Ref<T[]>
Реактивный массив элементов.
onReorder
(newItems, from, to) => void
Вызывается после успешного сброса с переупорядоченным массивом.
isDragDisabled
(item, index) => boolean
Верните true, чтобы запретить перетаскивание элемента.
scrollContainer
HTMLElement | Ref<HTMLElement | null>
Контейнер для авто-прокрутки при перетаскивании у его краёв.
Возвращаемое значение
dragIndex
Readonly<Ref<number>>
Индекс перетаскиваемого элемента, -1 в состоянии покоя.
overIndex
Readonly<Ref<number>>
Индекс текущей цели сброса.
isDragging
Readonly<Ref<boolean>>
Идёт ли сейчас перетаскивание.
ghostStyle
Readonly<Ref<CSSProperties>>
Стили с фиксированным позиционированием для призрачного элемента.
getItemStyle
(index: number) => CSSProperties
Стили для каждого элемента: opacity:0 для плейсхолдера, translateY для анимируемых соседей.
getItemProps
(index: number) => DraggableItemProps
Пропы для распространения на каждый перетаскиваемый элемент (data-drag-index, onPointerdown, CSS-классы). Перетаскивание полностью основано на pointer-событиях — это никогда не выставляет нативный HTML5-атрибут draggable, поэтому не провоцирует встроенное в браузер поведение drag-ghost/выделения текста.
CSS-классы, добавляемые getItemProps
| Класс | Когда |
|---|---|
vvsk-drag--dragging | Применяется к плейсхолдеру (перетаскиваемому элементу) |
vvsk-drag--over | Применяется к текущей цели сброса |
vvsk-drag--disabled | Применяется, когда isDragDisabled возвращает true |
Авто-прокрутка
Когда указан scrollContainer, список автоматически прокручивается вверх или вниз, когда курсор попадает в зону 60 px у краёв контейнера. Скорость прокрутки пропорциональна расстоянию (максимум 14 px за кадр).
Пример
<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>
<style>
.card {
padding: 12px 16px;
background: #fff;
border: 1px solid #ddd;
border-radius: 6px;
cursor: grab;
user-select: none;
}
.card--ghost {
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.3);
transform: scale(1.02);
pointer-events: none;
}
</style>