Копирование элементов при перетаскивании

Библиотека Interact.js предоставляет мощные инструменты для создания интерактивных веб-элементов, включая возможность перетаскивания, изменения размеров и жестов. Одной из важных задач является реализация копирования элементов при перетаскивании, когда исходный элемент остаётся на месте, а пользователь работает с его клоном.


Настройка перетаскиваемого элемента

Для начала необходимо сделать элемент перетаскиваемым:

interact('.draggable')
  .draggable({
    inertia: true,
    autoScroll: true,
    onstart: dragStartListener,
    onmove: dragMoveListener,
    onend: dragEndListener
  });
  • inertia: true — добавляет эффект инерции при завершении перетаскивания.
  • autoScroll: true — позволяет автоматически прокручивать контейнер при приближении курсора к границам.
  • onstart, onmove, onend — события жизненного цикла перетаскивания.

Создание клона при старте перетаскивания

Для копирования элемента нужно в обработчике onstart создавать новый DOM-элемент, идентичный исходному:

function dragStartListener(event) {
  const original = event.target;

  // Создание клона
  const clone = original.cloneNode(true);
  clone.classList.add('drag-clone');
  clone.style.position = 'absolute';
  clone.style.left = original.getBoundingClientRect().left + 'px';
  clone.style.top = original.getBoundingClientRect().top + 'px';
  document.body.appendChild(clone);

  // Сохраняем ссылку на клон для дальнейших перемещений
  event.interactable._currentClone = clone;
}

Особенности реализации:

  • Клон получает position: absolute, чтобы его можно было свободно перемещать по документу.
  • Координаты клона задаются через getBoundingClientRect() для точного позиционирования на месте оригинала.
  • Класс drag-clone позволяет стилизовать копию отдельно (например, изменить прозрачность или рамку).

Перемещение клона

В обработчике onmove перемещается именно клон, а не исходный элемент:

function dragMoveListener(event) {
  const clone = event.interactable._currentClone;
  if (!clone) return;

  const dx = event.dx;
  const dy = event.dy;

  const x = (parseFloat(clone.getAttribute('data-x')) || 0) + dx;
  const y = (parseFloat(clone.getAttribute('data-y')) || 0) + dy;

  clone.style.transform = `translate(${x}px, ${y}px)`;
  clone.setAttribute('data-x', x);
  clone.setAttribute('data-y', y);
}

Принцип работы:

  • Используется data-x и data-y для хранения текущего смещения.
  • transform: translate() обеспечивает плавное и производительное перемещение.
  • Не меняется исходный элемент — копия свободно перемещается по документу.

Завершение перетаскивания и фиксация копии

При завершении перетаскивания можно оставить клон на месте или удалить его, если перетаскивание отменено:

function dragEndListener(event) {
  const clone = event.interactable._currentClone;
  if (!clone) return;

  // Если клон перетащен в допустимую область, оставляем его
  if (isDropzoneValid(clone)) {
    clone.style.transform = '';
    clone.style.left = (parseFloat(clone.style.left) + parseFloat(clone.getAttribute('data-x') || 0)) + 'px';
    clone.style.top = (parseFloat(clone.style.top) + parseFloat(clone.getAttribute('data-y') || 0)) + 'px';
  } else {
    // Иначе удаляем клон
    clone.remove();
  }

  event.interactable._currentClone = null;
}

Особенности:

  • Проверка через функцию isDropzoneValid позволяет определить, оставлять ли элемент на странице.
  • Смещение через transform переводится в абсолютные координаты, чтобы клон корректно позиционировался.
  • Очистка _currentClone предотвращает утечки памяти при повторных перетаскиваниях.

Поддержка нескольких перетаскиваемых копий

Если требуется создавать несколько копий одновременно, лучше хранить массив клонированных элементов:

interact('.draggable').draggable({
  onstart(event) {
    const clone = event.target.cloneNode(true);
    clone.classList.add('drag-clone');
    document.body.appendChild(clone);

    if (!event.interactable._clones) {
      event.interactable._clones = [];
    }
    event.interactable._clones.push(clone);
    event.interactable._currentClone = clone;
  }
});
  • Массив _clones позволяет управлять всеми копиями, например, удалять их или проверять пересечения.
  • Такой подход полезен для интерфейсов типа досок, конструкторов и drag-and-drop форм.

Интеграция с Dropzone

Чтобы клон мог быть размещён в области Dropzone, используется комбинация draggable и dropzone:

interact('.dropzone').dropzone({
  accept: '.drag-clone',
  overlap: 0.75,
  ondrop: function(event) {
    const clone = event.relatedTarget;
    clone.style.position = 'relative';
    clone.style.transform = '';
    event.target.appendChild(clone);
  }
});
  • accept фильтрует элементы, которые можно сбросить в зону.
  • overlap: 0.75 определяет минимальное перекрытие для активации события ondrop.
  • После сброса элемент переводится в локальные координаты Dropzone для корректного отображения.

Дополнительные рекомендации

  • Для больших интерфейсов рекомендуется использовать requestAnimationFrame при обновлении transform, чтобы повысить производительность.
  • Клонирование сложных элементов с обработчиками событий требует глубокого клонирования (cloneNode(true)) и повторной инициализации интерактивных функций.
  • Стилизация клона через отдельный класс помогает отличать активный клон от оригинала и облегчает управление визуальными эффектами.

Эта структура позволяет создавать мощные системы перетаскивания с копированием элементов, которые сохраняют оригинальные объекты нетронутыми, поддерживают несколько копий и легко интегрируются с зонами сброса.