Если вы когда-нибудь пробовали сделать drag-and-drop на Vue по-настоящему гибким – с кастомным overlay, вложенными зонами, multi-drag и анимацией при отпускании – вы знаете, что большинство библиотек держат вас в клетке. Vue DnD Kit v2 эту клетку сломал.
Сегодня выходит v2. Рассказываю, что внутри и почему я уверен что это прям революция.
Composable API: любой элемент становится draggable за три строки
Никаких компонентов-обёрток и . В библиотеки — чистые composables, которые работают с любым элементом через ref:
Ваш div, ваша разметка, ваши классы. Библиотека только добавляет логику — не диктует структуру. А так же позволяет работать с компонентами других библиотек потому что используется ref.
То же для droppable:
makeDroppable(zoneRef, {
events: {
onDrop(e) {
const r = e.helpers.suggestSort('vertical');
if (r) items.value = r.targetItems as Item[];
}
}
}, () => items.value);
Умные хелперы: suggestSort вместо ручного splice
Самая болезненная часть любого DnD — логика «куда вставить элемент». Считать индексы, определять, выше или ниже курсора, делать splice… В v2 это решено на уровне API.
Каждый drop-event несёт объект helpers со всем нужным:
function onDrop(e: IDragEvent) {
const r = e.helpers.suggestSort('vertical'); // сам смотрит на позицию курсора
if (r) items.value = r.targetItems as Item[];
}
suggestSort анализирует, где именно курсор относительно элемента, и возвращает уже готовый новый массив. Никаких getBoundingClientRect в вашем коде.
Полный набор хелперов:
|
Хелпер |
Что делает |
|---|---|
|
|
Сортировка с определением позиции по курсору |
|
|
Обмен местами двух элементов |
|
|
Копирование в целевой список |
|
|
Удаление из исходного списка |
|
|
Низкоуровневая вставка |
|
|
Низкоуровневый обмен |
Но!) Вы всегда можете сами обработать все если вам это нужно, все хелперы лишь используют то вы получаете в event.
Перетаскивание между двумя списками — двенадцать строк с читабельной логикой:
function onDrop(e: IDragEvent) {
const r = e.helpers.suggestSort('vertical');
if (!r) return;
if (r.sameList) {
listA.value = r.targetItems as Item[];
} else {
if (r.sourceItems === listA.value) listA.value = r.sourceItems as Item[];
if (r.targetItems === listB.value) listB.value = r.targetItems as Item[];
}
}
Обратите внимание: r.sourceItems === listA.value — identity-сравнение массивов вместо поиска. O(1), никакого find.
Multi-drag из коробки
Выделяете несколько элементов — тащите всё сразу. Вы можете это сделать с помощью selected от драга и назначить на какой нибудь чекбокс через v-model или через makeSelectionArea которая создаёт область выделения прямо как на Windows:
e.draggedItems в обработчике drop содержит все выделенные элементы. suggestSort и suggestSwap корректно обрабатывают их без каких-либо изменений в вашем коде.
Деревья, Kanban, вложенные зоны
Вложенность — исторически слабое место DnD-библиотек. В v2 реализован чёткий алгоритм определения целевого массива: если курсор над draggable-элементом внутри droppable-зоны — вставляем в массив этого элемента, а не зоны. Это работает автоматически.
Дерево на v2:
function onDrop(e: IDragEvent) {
const r = e.helpers.suggestSort('vertical');
if (!r) return;
// r.targetItems уже указывает на нужный массив — children узла или корень
applyToTree(r.sourceItems, r.targetItems, r);
}
Никакого специального кода для определения глубины. Библиотека разбирается сама.
DragPreview: полная свобода в кастомизации overlay
DragPreview — встроенный компонент, который вы оборачиваете во что угодно. CSS , motion-v, GSAP — что хотите.
Простая CSS-анимация появления:
Spring-физика через motion-v — с анимацией появления и исчезновения:
AnimatePresence здесь ключевой: DragPreview использует v-if внутри, и без него анимация исчезновения просто не успеет сыграть — элемент уйдёт из DOM раньше.
Можно менять preview динамически — в зависимости от зоны под курсором:
Кастомный preview per-item
Каждый draggable может указать свой компонент для рендера в overlay:
makeDraggable(itemRef, {
render: markRaw(TaskCard),
data: () => ({ id: props.id, title: props.title, priority: props.priority }),
});
TaskCard рендерится внутри DragPreview и читает данные через useDnDProvider().entities — полный контроль над тем, как выглядит то, что тащит пользователь.
Остальное, что есть из коробки
Ограничения движения — только по оси или не выходить за пределы контейнера:
makeConstraintArea(containerRef, {
axis: 'y',
restrictToArea: true,
});
Автоскролл viewport и отдельных контейнеров:
...
makeAutoScroll(scrollableRef, { threshold: 80, speed: 1.5 });
Async drop — показываем диалог, preview ждёт результата:
function onDrop(e: IDragEvent) {
return new Promise((resolve, reject) => {
showConfirmDialog({
message: `Переместить "${e.draggedItems[0].item}"?`,
onConfirm: () => { applySort(e); resolve(); },
onCancel: reject,
});
});
}
Keyboard navigation — из коробки, с настраиваемыми клавишами.
Zero dependencies (кроме Vue 3), tree-shakeable, полная TypeScript типизация.
Попробовать
npm install @vue-dnd-kit/core
Всех желающий ознакомится приглашаю к себе в репозиторий и в документацию))
Документация и playground: vue-dnd-kit.dev
GitHub: github.com/zizigy/vue-dnd-kit
Если попробуете и найдёте что-то странное — открывайте issue, я читаю всё. Звёзды тоже принимаю 🙂
А так же любую помощь <3


