Руководство
EffDND добавляет функцию drag-and-drop в ваш HTML-код, используя только атрибуты. Никаких вызовов JavaScript, никакой настройки, кроме одного импорта. В этом руководстве объясняется, что вы можете с ним делать и как.
Начало работы
npm i effdndИмпортируйте один раз в ваше приложение:
import 'effdnd'; // библиотека
import 'effdnd/style.css'; // необязательно: стили по умолчаниюВот и всё. Любой элемент, отмеченный атрибутами ниже, становится перетаскиваемым немедленно — даже элементы, добавленные на страницу позже.
Фреймворки: работает с любым. Оборачивать компоненты не требуется.
Как это работает
EffDND построен на одной основной идее, а всё остальное в этом руководстве — лишь её расширение:
- Перетаскивание запускается только с помощью
trigger. Само по себе перетаскивание не включается. Вы захватываете элемент, помеченный какdata-dnd-trigger, и только тогда начинается drag-and-drop; без него ничего не движется. - Действительно перемещается
item. Триггер обычно находится внутри элемента, поэтому весьitemследует за вашим указателем, но они не обязательно должны быть одним и тем же узлом. scope— это песочница.itemможет перемещаться и быть сброшен только в пределах своей области видимости. Зоны сброса или контейнеры за её пределами просто игнорируются.
Это базовый случай: trigger на item внутри scope.
<div data-dnd-scope="board"> <!-- scope: the sandbox -->
<div data-dnd-item="a"> <!-- item: what moves -->
<span data-dnd-trigger>⠿</span> <!-- trigger: what you grab -->
Grab me
</div>
</div>Каждая последующая функция — изменение порядка, перемещение, прокрутка или параметры запуска, указанные ниже — лишь уточняет или расширяет это базовое поведение.
Стилизация при перетаскивании. Перемещаемый элемент — это клон (копия) элемента, вставленный в родительский элемент. Это означает, что каскадные и унаследованные стили и правила, написанные для родительских селекторов (
ul > li,.zone .card, …), по-прежнему применяются к перемещаемому клону — нет необходимости встраивать каждый цвет. Оригинальный элемент остается на месте в списке и затемняется с помощью состоянияpassive(см. Стилизация ниже).
Предостережение — преобразованные элементы выше в DOM Клон имеет
position:fixed, поэтому он отсоединен от макета. Но если какой-либо родитель элемента имеет атрибутыtransform,filter,perspective(илиwill-change:transform), браузер обрабатываетfixedкакabsolute: клон тогда позиционируется относительно этого родительского элемента и может выглядеть смещенным (и будет прокручиваться с помощью прокручиваемого контейнера, преобразованного с помощьюtransform). Избегайте использования преобразованных родительских элементов перетаскиваемых элементов или переопределяйте стилиfixed-visualс помощьюdata-dnd-stateи встроенных стилей, которые вы применяете к элементу.
Краткий обзор атрибутов
| Атрибут | Назначение |
|---|---|
data-dnd-trigger | Отмечает «маркер», который пользователь перетаскивает |
data-dnd-item | Отмечает элемент, который перетаскивается |
data-dnd-scope | Ограничивает область перетаскивания одним контейнером |
data-dnd-reorder | Преобразует контейнер в сортируемый список (x или y) |
data-dnd-target | Отмечает цель перетаскивания |
data-dnd-transfer | Задает цели перетаскивания действие: append, prepend или remove |
data-dnd-scroll | Автоматически прокручивает контейнер во время перетаскивания |
data-dnd-transition | Задает анимацию перемещения |
data-dnd-disabled | Отключает триггер |
Для самого простого случая требуются только три из них: триггер (trigger) элемента (item) внутри контейнера (scope). Все остальное необязательно и добавляет поведение.
Параметры триггера
Поскольку trigger является точкой входа, это атрибут, который вы настраиваете чаще всего. Он принимает список параметров, разделенных точкой с запятой:
<span data-dnd-trigger="dist:12;axis:y;scope:board;item:task-1">⠿</span>Требуется указать расстояние срабатывания (dist)
Предотвращает случайное перетаскивание по щелчку — перетаскивание происходит только после того, как указатель переместится на заданное количество пикселей:
<span data-dnd-trigger="dist:12">⠿</span> <!-- Перетаскивание происходит только после перемещения указателя на 12 пикселей -->Фиксация на одной оси (axis)
<span data-dnd-trigger="axis:x">⠿</span> <!-- только горизонтальное перемещение -->
<span data-dnd-trigger="axis:y">⠿</span> <!-- только вертикальное перемещение -->Укажите конкретный контейнер (scope)
По умолчанию EffDND находит ближайший scope, анализируя DOM. Если вам нужен конкретный контейнер, укажите его имя:
<span data-dnd-trigger="scope:board">⠿</span>Укажите конкретный элемент (item)
Триггер может указать, какой именно элемент над ним нужно переместить:
<span data-dnd-trigger="scope:board;item:task-1">⠿</span>Ограничить выбор целей (target)
Разрешить сброс только в зоны, имя которых в параметре data-dnd-target начинается с указанного префикса:
<span data-dnd-trigger="scope:board;target:drop">⠿</span>Свободное перемещение по всей странице (scope:*)
Специальное значение scope:* полностью игнорирует любые границы scope:
<span data-dnd-trigger="scope:*">⠿</span>Затем элемент можно перетащить в любое место на странице.
Отключение триггера
<span data-dnd-trigger data-dnd-disabled>⠿</span>Удалите атрибут, чтобы снова включить его.
Типичные сценарии
Это распространенные способы взаимодействия элементов в реальных интерфейсах.
Переупорядочивание: сортируемый список
Оберните элементы в список с помощью data-dnd-reorder="y", отметьте каждый элемент и добавьте атрибуты.
<ul data-dnd-scope="todo" data-dnd-reorder="y">
<li data-dnd-item="1"><span data-dnd-trigger>⠿</span>Buy milk</li>
<li data-dnd-item="2"><span data-dnd-trigger>⠿</span>Read a book</li>
</ul>yсортирует по вертикали,xсортирует по горизонтали.- переставляются только элементы, являющиеся прямыми дочерними элементами списка.
Перемещение: перемещение предметов между контейнерами
Пометьте контейнер как зону сброса и задайте ему действие перемещения.
<div data-dnd-scope="sprint">
<div class="zone" data-dnd-target="backlog" data-dnd-transfer="append">Backlog</div>
<div class="zone" data-dnd-target="done" data-dnd-transfer="prepend">Done</div>
</div>append— элемент добавляется в конец зоны.prepend— элемент добавляется в начало зоны.remove— элемент удаляется (отлично подходит для корзины).
Элементы target должны находиться в пределах одной и той же области видимости scope (или использовать scope:*, см. выше).
Комбинируем: сортируемый список со встроенной корзиной
<div data-dnd-scope="mailbox">
<ul data-dnd-reorder="y">
<li data-dnd-item="1"><span data-dnd-trigger>⠿</span>Invoice</li>
<li data-dnd-item="2"><span data-dnd-trigger>⠿</span>Newsletter</li>
</ul>
<button data-dnd-target="trash" data-dnd-transfer="remove">Delete</button>
</div>Здесь список сортируется сам собой, и строки можно удалить, перетащив их на кнопку.
Канбан: столбцы и карточки
Для сортировки столбцов назначьте доске data-dnd-reorder="x", а для области карточек каждого столбца — вертикальную зону переупорядочивания и target атрибуты.
<div data-dnd-scope="kanban" data-dnd-reorder="x"> <!-- сортировка столбцов -->
<div class="col" data-dnd-item="col-1">
<div class="col-head"><span data-dnd-trigger>⠿</span>Backlog</div>
<div class="cards" data-dnd-reorder="y"
data-dnd-target="col-1" data-dnd-transfer="append"> <!-- сортирует и принимает карты -->
<div class="card" data-dnd-item="task-1"><span data-dnd-trigger>⠿</span>Write spec</div>
</div>
</div>
<!-- ещё столбцы ... -->
</div>Перетащите заголовок столбца, чтобы изменить порядок столбцов; перетащите карточку, чтобы переместить ее между столбцами.
Настройка стандартных функций
Автоматическая прокрутка длинного контейнера
Прикрепите data-dnd-scroll к прокручиваемому элементу:
<div data-dnd-scroll="threshold:50;speed:12">threshold(по умолчанию30) — насколько близко к краю начинается прокрутка, в пикселях.speed(по умолчанию10) — насколько быстро происходит прокрутка, в пикселях за кадр.
Перетаскивание вблизи края теперь прокручивает контейнер, и скорость прокрутки увеличивается по мере приближения к краю.
Сглаживание анимации
<div data-dnd-transition="150ms ease">…</div>Значение по умолчанию 100ms linear.
Реагирование на перетаскивания из JavaScript
Вы по-прежнему можете это сделать — EffDND просто делает это необязательным. Каждая из приведенных ниже функций возвращает функцию отмены подписки:
import { onDrag, onDrop, onReorder, onTransfer, onDragStart, onDragEnd } from 'effdnd';
onReorder((event) => {
console.log('Item was reordered:', event.detail.keys.item);
});Доступные события: effdragstart, effdrag, effdragend, effdragenter, effdragleave, effdrop, effreorder, efftransfer.
Каждое событие предоставляет вам объект detail, на который вы можете положиться:
event.detail.keys.item; // какой item
event.detail.keys.scope; // в каком scope
event.detail.keys.target; // на какой target
event.detail.item;Утилиты, которые могут пригодиться
import { getItem, getScope, getTargets, getReorderContainer, reset } from 'effdnd';
getItem(trigger); // the item an element belongs to
getScope(trigger); // the scope an element belongs to
getTargets(trigger); // the drop zones available for a trigger
reset(item); // snap an item back to its original positionСтилизация с помощью data-dnd-state
Во время перетаскивания EffDND помечает задействованные элементы атрибутом data-dnd-state, активируемым во время выполнения. Это обеспечивает удобный механизм для стилизации активного перетаскиваемого элемента — встроенный JavaScript не требуется. Атрибут data-dnd-state отсутствует, когда ничего не перетаскивается.
Значения состояния
| Элемент | Состояние | Значение |
|---|---|---|
data-dnd-item | active | Движущийся клон — элемент, который в данный момент следует за вашим указателем |
data-dnd-item | passive | Исходный элемент, оставленный на месте, затемнённый за клоном |
data-dnd-scope | active | Указатель находится за пределами области видимости |
data-dnd-scope | passive | Указатель находится внутри области видимости — в обычном режиме при перетаскивании |
data-dnd-target | active | Зона перетаскивания, на которую в данный момент наведён курсор |
data-dnd-target | passive | Действительная зона перетаскивания, готовая принять перетаскивание |
Встроенный файл index.css уже содержит удобные настройки по умолчанию, основанные на этих селекторах, и вы можете использовать те же самые селекторы для их переопределения:
/* Значения по умолчанию взяты из index.css */
[data-dnd-item][data-dnd-state="passive"] { opacity: 0.2; }
[data-dnd-item][data-dnd-state="active"] { opacity: 0.75; z-index: 1000; }
/* Ваша собственная тема */
[data-dnd-item][data-dnd-state="passive"] { opacity: 0.08; }
[data-dnd-item][data-dnd-state="active"] {
opacity: 1;
box-shadow: 0 0 0 2px cornflowerblue;
border-radius: 8px;
}
[data-dnd-scope][data-dnd-state="active"] { outline: 2px dashed tomato; }
[data-dnd-target][data-dnd-state="active"] { background: rgba(100, 200, 255, 0.2); }Поскольку настройки по умолчанию находятся в index.css, их загрузка необязательна: если вы пропустите этот импорт и напишете свои собственные правила [data-dnd-state="…"], EffDND останется полностью независимым от зависимостей, и вы сохраните полный визуальный контроль.
Совет. Оригинал затемняется параметром
opacity, а не удаляется — поэтому список сохраняет свою структуру, и соседние элементы не смещаются во время перетаскивания. Если вы хотите, чтобы оригинал полностью исчез, установитеopacity: 0(илиvisibility: hidden) для состоянияpassive.
Краткий справочник
Сокращенная версия каждого атрибута и его параметров.
| Атрибут / параметр | Значения | По умолчанию | Назначение |
|---|---|---|---|
data-dnd-trigger | — | — | Отмечает маркер, запускающий перетаскивание |
… dist | число | — | Перетаскивать только после того, как указатель переместится на указанное количество пикселей |
… axis | x / y | оба | Заблокировать перетаскивание по одной оси |
… scope | имя / * | ближайший в DOM | Использовать определенный контейнер (или игнорировать области видимости) |
… item | имя | ближайший в DOM | Перетащить определенный элемент |
… target | имя | все | Разрешить перетаскивание только на цели, соответствующие имени |
data-dnd-disabled | — | — | Отключает триггер |
data-dnd-item | уникальное имя | — | Отмечает элемент, который перетаскивается |
data-dnd-scope | уникальное имя | — | Ограничивает перетаскивание одним контейнером |
data-dnd-reorder | x / y | — | Преобразует контейнер в сортируемый список |
data-dnd-target | имя | — | Отмечает зону перетаскивания |
data-dnd-transfer | append / prepend / remove | — | Действие, выполняемое при перетаскивании в зону |
data-dnd-scroll | threshold;speed | 30;10 | Автоматическая прокрутка контейнера во время перетаскивания |
data-dnd-transition | длительность и сглаживание | 100ms linear | Сглаживает анимацию движения |
data-dnd-state | active / passive | — | Состояние перетаскивания во время выполнения, стилизуемое с помощью CSS (см. Стилизация) |