Skip to content

Руководство

EffDND добавляет функцию drag-and-drop в ваш HTML-код, используя только атрибуты. Никаких вызовов JavaScript, никакой настройки, кроме одного импорта. В этом руководстве объясняется, что вы можете с ним делать и как.

Начало работы

sh
npm i effdnd

Импортируйте один раз в ваше приложение:

ts
import 'effdnd';            // библиотека
import 'effdnd/style.css';  // необязательно: стили по умолчанию

Вот и всё. Любой элемент, отмеченный атрибутами ниже, становится перетаскиваемым немедленно — даже элементы, добавленные на страницу позже.

Фреймворки: работает с любым. Оборачивать компоненты не требуется.

Как это работает

EffDND построен на одной основной идее, а всё остальное в этом руководстве — лишь её расширение:

  • Перетаскивание запускается только с помощью trigger. Само по себе перетаскивание не включается. Вы захватываете элемент, помеченный как data-dnd-trigger, и только тогда начинается drag-and-drop; без него ничего не движется.
  • Действительно перемещается item. Триггер обычно находится внутри элемента, поэтому весь item следует за вашим указателем, но они не обязательно должны быть одним и тем же узлом.
  • scope — это песочница. item может перемещаться и быть сброшен только в пределах своей области видимости. Зоны сброса или контейнеры за её пределами просто игнорируются.

Это базовый случай: trigger на item внутри scope.

html
<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 является точкой входа, это атрибут, который вы настраиваете чаще всего. Он принимает список параметров, разделенных точкой с запятой:

html
<span data-dnd-trigger="dist:12;axis:y;scope:board;item:task-1">⠿</span>

Требуется указать расстояние срабатывания (dist)

Предотвращает случайное перетаскивание по щелчку — перетаскивание происходит только после того, как указатель переместится на заданное количество пикселей:

html
<span data-dnd-trigger="dist:12">⠿</span>   <!-- Перетаскивание происходит только после перемещения указателя на 12 пикселей -->

Фиксация на одной оси (axis)

html
<span data-dnd-trigger="axis:x">⠿</span>   <!-- только горизонтальное перемещение -->
<span data-dnd-trigger="axis:y">⠿</span>   <!-- только вертикальное перемещение -->

Укажите конкретный контейнер (scope)

По умолчанию EffDND находит ближайший scope, анализируя DOM. Если вам нужен конкретный контейнер, укажите его имя:

html
<span data-dnd-trigger="scope:board">⠿</span>

Укажите конкретный элемент (item)

Триггер может указать, какой именно элемент над ним нужно переместить:

html
<span data-dnd-trigger="scope:board;item:task-1">⠿</span>

Ограничить выбор целей (target)

Разрешить сброс только в зоны, имя которых в параметре data-dnd-target начинается с указанного префикса:

html
<span data-dnd-trigger="scope:board;target:drop">⠿</span>

Свободное перемещение по всей странице (scope:*)

Специальное значение scope:* полностью игнорирует любые границы scope:

html
<span data-dnd-trigger="scope:*">⠿</span>

Затем элемент можно перетащить в любое место на странице.

Отключение триггера

html
<span data-dnd-trigger data-dnd-disabled>⠿</span>

Удалите атрибут, чтобы снова включить его.

Типичные сценарии

Это распространенные способы взаимодействия элементов в реальных интерфейсах.

Переупорядочивание: сортируемый список

Оберните элементы в список с помощью data-dnd-reorder="y", отметьте каждый элемент и добавьте атрибуты.

html
<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 сортирует по горизонтали.
  • переставляются только элементы, являющиеся прямыми дочерними элементами списка.

Перемещение: перемещение предметов между контейнерами

Пометьте контейнер как зону сброса и задайте ему действие перемещения.

html
<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:*, см. выше).

Комбинируем: сортируемый список со встроенной корзиной

html
<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 атрибуты.

html
<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 к прокручиваемому элементу:

html
<div data-dnd-scroll="threshold:50;speed:12">
  • threshold (по умолчанию 30) — насколько близко к краю начинается прокрутка, в пикселях.
  • speed (по умолчанию 10) — насколько быстро происходит прокрутка, в пикселях за кадр.

Перетаскивание вблизи края теперь прокручивает контейнер, и скорость прокрутки увеличивается по мере приближения к краю.

Сглаживание анимации

html
<div data-dnd-transition="150ms ease">…</div>

Значение по умолчанию 100ms linear.

Реагирование на перетаскивания из JavaScript

Вы по-прежнему можете это сделать — EffDND просто делает это необязательным. Каждая из приведенных ниже функций возвращает функцию отмены подписки:

ts
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, на который вы можете положиться:

ts
event.detail.keys.item;          // какой item
event.detail.keys.scope;         // в каком scope
event.detail.keys.target;        // на какой target
event.detail.item;

Утилиты, которые могут пригодиться

ts
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-itemactiveДвижущийся клон — элемент, который в данный момент следует за вашим указателем
data-dnd-itempassiveИсходный элемент, оставленный на месте, затемнённый за клоном
data-dnd-scopeactiveУказатель находится за пределами области видимости
data-dnd-scopepassiveУказатель находится внутри области видимости — в обычном режиме при перетаскивании
data-dnd-targetactiveЗона перетаскивания, на которую в данный момент наведён курсор
data-dnd-targetpassiveДействительная зона перетаскивания, готовая принять перетаскивание

Встроенный файл index.css уже содержит удобные настройки по умолчанию, основанные на этих селекторах, и вы можете использовать те же самые селекторы для их переопределения:

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числоПеретаскивать только после того, как указатель переместится на указанное количество пикселей
… axisx / yобаЗаблокировать перетаскивание по одной оси
… scopeимя / *ближайший в DOMИспользовать определенный контейнер (или игнорировать области видимости)
… itemимяближайший в DOMПеретащить определенный элемент
… targetимявсеРазрешить перетаскивание только на цели, соответствующие имени
data-dnd-disabledОтключает триггер
data-dnd-itemуникальное имяОтмечает элемент, который перетаскивается
data-dnd-scopeуникальное имяОграничивает перетаскивание одним контейнером
data-dnd-reorderx / yПреобразует контейнер в сортируемый список
data-dnd-targetимяОтмечает зону перетаскивания
data-dnd-transferappend / prepend / removeДействие, выполняемое при перетаскивании в зону
data-dnd-scrollthreshold;speed30;10Автоматическая прокрутка контейнера во время перетаскивания
data-dnd-transitionдлительность и сглаживание100ms linearСглаживает анимацию движения
data-dnd-stateactive / passiveСостояние перетаскивания во время выполнения, стилизуемое с помощью CSS (см. Стилизация)

Опубликовано под лицензией Apache License 2.0