Документация/Руководства
Оси, границы и сетки
Фиксируйте драги по одной оси, удерживайте их внутри блока, привязывайте к сетке или к собственным направляющим — для одного элемента, списка или целой части страницы.
Страница переведена ИИ и может содержать неточности. Читать английский оригинал
Драг можно удержать на одной оси, внутри блока, привязать к сетке или изменить собственным правилом. Правила — это модификаторы: они корректируют смещение драга на каждом кадре. Задайте их на одном перетаскиваемом элементе, на списке или на части страницы, и оверлей, цели и бросок будут им следовать.
Фиксация оси
Клипы на таймлайне скользят по своей дорожке. Выключите фиксацию, и они смогут менять и дорожку.
Загружаем демо…
<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import Timeline from './Timeline.vue';
</script>
<template>
<DnDProvider>
<Timeline />
</DnDProvider>
</template>const { isDragging } = makeDraggable(el, {
id: () => props.clip.id,
axis: () => (props.locked ? 'x' : undefined),
});
axis: 'x' двигает только по горизонтали, 'y' — только по вертикали. Её принимают
makeDraggable, makeSortableList, makeSortableTree, makeSortableItem и makeDragArea:
makeSortableList(listRef, { items: tracks, axis: 'y' });
- Фиксация срабатывает после всех модификаторов, поэтому держит, что бы они ни делали: привязка или граница не могут сдвинуть элемент с его линии.
- Линия задана в содержимом, а не на экране: она движется вместе со списком (или родителем), в котором начался драг. Прокрутите страницу посреди драга — и элемент останется на своей строке.
- Собственный
axisэлемента важнее, чем у его списка или дерева, аaxisперетаскиваемого элемента (или его списка) важнее, чем у его области перетаскивания. - Он читается в начале драга, поэтому геттер может его переключать, как выше.
- Дерево, зафиксированное по
'y', всё равно вкладывает: глубина тогда берётся из наведения на середину строки.
axis модификаторам restrictToVerticalAxis и restrictToHorizontalAxis. Они
делают то же самое, но как обычные модификаторы работают в середине цепочки, и модификатор
после них (сетка, привязывающая обе оси) может снова сдвинуть элемент с оси.Модификаторы
Модификатор корректирует смещение драга на каждом кадре. Его результат двигает оверлей и
служит точкой, в которой ищутся цели, поэтому список сортирует по зафиксированной,
ограниченной или привязанной позиции, а position броска — там, где оказался блок.
| Модификатор | Что делает |
|---|---|
restrictToParent | Удерживает оверлей внутри контейнера, в котором начался драг: сортируемого списка или родительского элемента обычного перетаскиваемого элемента. |
restrictToElement(target) | Удерживает оверлей внутри элемента: template ref, геттера или самого элемента. |
restrictToWindowEdges | Удерживает оверлей внутри окна. |
restrictToFirstScrollableAncestor | Удерживает оверлей внутри первого прокручиваемого предка перетаскиваемого элемента. |
snapToGrid(size) | Привязывает оверлей к сетке (ниже). |
snapCenterToCursor | Центрирует оверлей под указателем вместо сохранения смещения захвата. |
restrictToVerticalAxis | Двигает только по вертикали. Лучше axis: 'y'. |
restrictToHorizontalAxis | Двигает только по горизонтали. Лучше axis: 'x'. |
modifiers — опция провайдера, областей перетаскивания, списков и деревьев, перетаскиваемых
и сортируемых элементов. Все, кроме провайдера, принимают также ref или геттер, который
читается в начале драга.
Удержать внутри
Собственное видео в видеозвонке остаётся на экране. Переключите границу и утащите его подальше:
Загружаем демо…
<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import CallScreen from './CallScreen.vue';
</script>
<template>
<DnDProvider>
<CallScreen />
</DnDProvider>
</template>const { isDragging } = makeDraggable(el, {
id: 'self-view',
modifiers: () => props.modifiers, // [restrictToParent], [restrictToWindowEdges] or []
});
restrictToParentдержит его на экране, его родительском элементе. Для сортируемого элемента «родитель» — его список, где бы список ни находился в DOM.restrictToWindowEdgesпозволяет ему покинуть экран, но не окно. Брошенное за пределами экрана, оно не над целью, поэтому летит обратно.- Без границы оно двигается куда угодно.
Граница удерживает внутри оверлей, в его текущем размере: с overlayFit: 'slot' блок,
который растёт над списком более широких элементов, удерживается внутри в новом размере.
Привязка к сетке
Узлы блок-схемы привязываются к сетке холста. Переключите размер ячейки и плавность:
Загружаем демо…
<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import Flowchart from './Flowchart.vue';
</script>
<template>
<DnDProvider>
<Flowchart />
</DnDProvider>
</template>const { isDragging } = makeDraggable(el, {
id: () => props.node.id,
modifiers: () => [snapToGrid({ x: props.grid, y: props.grid, smooth: props.smooth })],
});
snapToGrid(24) привязывает обе оси к ячейкам 24 px. Объект задаёт больше:
| Опция | Значение |
|---|---|
x, y | Размер ячейки в пикселях, для каждой оси. Не задайте одну, чтобы привязывать только другую: { y: 15 } привязывает событие к четвертям часа и даёт ему свободно двигаться по дням. |
origin | 'container' (по умолчанию): левый верхний угол оверлея попадает на линии сетки, проведённые от левого верхнего угла контейнера — сортируемого списка или родителя обычного перетаскиваемого элемента. 'delta': округляется само смещение, так что элемент, начавший вне сетки, вне неё и остаётся и двигается на целые ячейки от того места, где был. Выбирайте его для элементов, размещённых где угодно (свободный холст), которые должны сохранять собственное выравнивание. |
smooth | true или { duration, easing }: оверлей плавно скользит между ячейками (по умолчанию 80 ms). Цели сразу используют привязанную позицию. |
С origin: 'container' рисуйте сетку от border box контейнера: у холста выше рамка сделана
через box-shadow, а не border, поэтому его линии начинаются у самого края.
Свой модификатор
Блоки на слайде прыгают на края и центры других, когда подходят близко, а розовые направляющие показывают линии, на которых они стоят:
Загружаем демо…
<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import Slide from './Slide.vue';
</script>
<template>
<DnDProvider>
<Slide />
</DnDProvider>
</template>import type { Modifier } from '@vue-dnd-kit/core';
/** Near a line, the box jumps onto it. */
const magnet: Modifier = ({ transform, overlayRect, containerRect, active }) => {
if (!containerRect) return transform;
const id = String(active.id);
const left = overlayRect.left - containerRect.left;
const top = overlayRect.top - containerRect.top;
return {
x: transform.x + pull(left, overlayRect.width, lines(id, 'x')),
y: transform.y + pull(top, overlayRect.height, lines(id, 'y')),
};
};
// In order: the magnet pulls, then the slide's edges hold.
const modifiers = [magnet, restrictToParent];
Модификатор получает transform — смещение от начала драга в том виде, в каком его оставили
предыдущие модификаторы, — и возвращает новое. Он также получает то, что может понадобиться
для вычисления:
| Аргумент | Что это |
|---|---|
transform | Смещение на данный момент. |
overlayRect | Блок оверлея, сдвинутый на transform, в его текущем размере. |
activeRect | Перетаскиваемый элемент в момент начала драга. |
containerRect | Контейнер, в котором начался драг (сортируемый список или родительский элемент), там, где он сейчас: он следует за прокруткой. |
windowRect | Окно. |
origin | Указатель в момент начала драга. |
active, over | Драг и цель под ним. |
Модификаторы выполняются на каждом кадре: держите их быстрыми и без побочных эффектов.
Направляющие выше рисуются по delta драга в onDragMove монитора — это смещение после всех
модификаторов. Типы — в модификаторах.
Порядок
Модификаторы из нескольких мест выполняются один за другим, каждый от результата предыдущего:
modifiersпровайдера;modifiersобласти перетаскивания, в которой начался драг;- собственные
modifiersперетаскиваемого элемента или, если их нет, его списка или дерева; restrictToAreaобласти перетаскивания;- фиксация
axis.
Внутри одного массива они выполняются по порядку, поэтому ставьте привязки и магниты перед
границами: граница в конце удерживает блок внутри, что бы ни сделали остальные. Слайд выше
выполняет [magnet, restrictToParent]; в обратном порядке магнит мог бы утянуть блок,
удерживаемый у края слайда, на несколько пикселей за край.
modifiers элемента заменяют модификаторы его списка, а не добавляются к ним. Границы
области идут после привязок шагов 1–3, поэтому привязанный элемент всё равно остаётся внутри,
а фиксация оси идёт самой последней.
Ограниченные списки сохраняют свои края
Панель слоёв: строки двигаются только вверх и вниз и никогда не покидают панель. Перетащите верхнюю строку в самый низ:
Загружаем демо…
<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import Layers from './Layers.vue';
</script>
<template>
<DnDProvider>
<Layers />
</DnDProvider>
</template>const { items } = makeSortableList(list, { items: layers, axis: 'y', modifiers: [restrictToParent] });
Граница удерживает оверлей у края блока, пока указатель движется дальше. Как только указатель покинул оверлей вдоль оси списка, список сортирует уже по указателю, так что первое и последнее место остаются достижимыми, как бы вы ни держали элемент. Сетки продолжают сортировать по оверлею: удерживаемый у края, он уже накрывает крайнюю ячейку.
Правила для части страницы
Две области, два набора правил. Стикеры в Layout привязываются к её сетке и не могут её покинуть; стикеры в Sketch ходят куда угодно, в том числе в Layout. Драг сохраняет правила области, в которой начался, а область, чьи правила действуют, подсвечивается.
Загружаем демо…
<script setup lang="ts">
import { ref } from 'vue';
import { DnDProvider, type Coordinates, type Key } from '@vue-dnd-kit/core';
import DragArea from './DragArea.vue';
const stickers = ref([
{ id: 'logo', text: 'Logo', color: '#42b883', area: 'sketch', x: 20, y: 24 },
{ id: 'photo', text: 'Photo', color: '#3b82f6', area: 'sketch', x: 110, y: 96 },
{ id: 'quote', text: 'Quote', color: '#ec4899', area: 'sketch', x: 36, y: 150 },
{ id: 'title', text: 'Title', color: '#f59e0b', area: 'layout', x: 24, y: 24 },
{ id: 'button', text: 'Button', color: '#a855f7', area: 'layout', x: 120, y: 144 },
]);
const place = (area: string, id: Key, at: Coordinates) => {
const sticker = stickers.value.find((item) => item.id === id);
if (!sticker) return;
sticker.area = area;
sticker.x = Math.round(at.x);
sticker.y = Math.round(at.y);
};
</script>
<template>
<DnDProvider>
<div class="studio">
<DragArea
id="sketch"
title="Sketch"
rules="no rules"
:stickers="stickers.filter((sticker) => sticker.area === 'sketch')"
@place="place"
/>
<DragArea
id="layout"
title="Layout"
rules="snapToGrid(24) · restrictToArea"
strict
:stickers="stickers.filter((sticker) => sticker.area === 'layout')"
@place="place"
/>
</div>
</DnDProvider>
</template>
<style scoped>
.studio {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
gap: 12px;
width: 100%;
max-width: 560px;
margin: 0 auto;
}
</style><script setup lang="ts">
const area = useTemplateRef<HTMLElement>('area');
const { isDragging } = makeDragArea(area, {
modifiers: () => (props.strict ? [snapToGrid(24)] : []),
restrictToArea: () => props.strict,
});
</script>
<template>
<div ref="area" class="area" :class="{ busy: isDragging }">
<Sticker v-for="sticker in stickers" :key="sticker.id" :sticker="sticker" />
</div>
</template>
makeDragArea задаёт правила для драгов, которые начинаются внутри её элемента, — и для
обычных перетаскиваемых элементов, и для сортируемых, которым тогда не нужны собственные
правила. Держите один DnDProvider на всё приложение, а доске, холсту или ленте дайте
собственную область.
| Опция | Что делает |
|---|---|
axis | Фиксирует драги по одной оси. Собственный axis перетаскиваемого элемента или его списка важнее. |
modifiers | Выполняются после модификаторов провайдера и до собственных модификаторов перетаскиваемого элемента. |
restrictToArea | true: драги остаются внутри элемента области. |
autoScroll | Автоскролл скролл-контейнера области (самого элемента или его ближайшего прокручиваемого предка). false отключает его там. |
activation | Активация для каждого типа указателя, например более долгое нажатие касанием в прокручиваемой ленте. Собственная активация перетаскиваемого элемента важнее; активация провайдера — последняя. |
disabled | В области не начинается ни один драг, как и в областях внутри неё. Её перетаскиваемые элементы сообщают aria-disabled. |
Каждая опция принимает значение, ref или геттер, который читается в начале нажатия или драга.
Область возвращает isDragging (идёт драг, начавшийся здесь, — это показывает подсвеченная
рамка выше) и isAutoScrolling (автоскролл прокручивает её контейнер). Полный справочник:
makeDragArea.
Как сочетаются области
- Драг следует правилам области, в которой начался, даже после того как покинул её.
Проход над областью ничего не меняет, кроме её
autoScroll: он применяется к каждому драгу, который прокручивает её контейнер. - Вложенные области не сливаются: побеждает ближайшая. Исключение —
disabled, и оно наследуется: выключенная область выключает и области внутри себя. - Определение цели, клавиши клавиатуры и объявления остаются общими для провайдера.
DnDProvider вокруг доски? Провайдеры независимы: драг никогда не переходит
из одного в другой, так что элементы доски никогда не смогли бы её покинуть. Вкладывайте
провайдер только тогда, когда части страницы нужны собственные настройки на уровне провайдера
и её элементы остаются внутри неё. См. Провайдер.Оставить там, где бросили
Таймлайн, звонок, блок-схема, слайд и области выше — все оставляют каждый элемент там, где его
бросили. Каждое событие драга несёт position: левый верхний угол перетаскиваемого блока
внутри цели, от края её padding и с учётом её прокрутки — те left и top, которые ставят
абсолютно позиционированный элемент ровно туда. Он вычисляется после модификаторов, поэтому
привязанный узел приземляется на сетку.
makeDroppable(canvas, { id: 'canvas' });
useDnDMonitor({
onDragEnd({ active, over, position }) {
const node = nodes.value.find((item) => item.id === active.id);
if (!node || over?.id !== 'canvas' || !position) return;
node.x = Math.round(position.x);
node.y = Math.round(position.y);
},
});
Подробнее о свободном перетаскивании и целях — в Drag and drop.