Proxy паттерны

Библиотека Shopify Draggable реализует перетаскивание элементов через систему модульных плагинов. Одним из ключевых механизмов визуального взаимодействия является Proxy‑паттерн, позволяющий отображать во время перетаскивания не сам исходный элемент, а его специальную копию — прокси‑элемент.

Прокси используется для:

  • визуального клонирования перетаскиваемого элемента
  • изменения поведения перемещения без изменения оригинального DOM‑узла
  • управления позицией, стилями и слоями отображения
  • предотвращения смещения элементов в исходном контейнере

Во время drag‑операции создаётся виртуальное представление элемента, которое следует за курсором, тогда как исходный элемент может:

  • оставаться на месте
  • скрываться
  • получать вспомогательные стили
  • заменяться placeholder‑элементом

Такая архитектура обеспечивает чистоту DOM, высокую производительность и гибкость кастомизации.


Назначение Proxy‑плагина

Proxy‑модуль в Draggable реализует промежуточный слой между:

  • исходным элементом (source)
  • визуальным элементом перетаскивания (proxy)
  • координатами указателя

Схема взаимодействия:

DragStart
   ↓
Создание Proxy
   ↓
Следование Proxy за курсором
   ↓
Drop
   ↓
Удаление Proxy

Преимущества подхода:

1. Отделение логики от представления

Оригинальный элемент остаётся в контейнере и не требует перемещения по DOM.

2. Управление производительностью

Перемещение одного прокси‑элемента дешевле, чем сложные операции с множеством контейнеров.

3. Гибкость визуализации

Прокси может:

  • иметь другой размер
  • иметь прозрачность
  • отображать дополнительные элементы
  • анимироваться независимо.

Подключение Proxy‑плагина

Proxy подключается как дополнительный плагин при инициализации Draggable.

Пример подключения:

import {Draggable, Plugins} from '@shopify/draggable';

const containers = document.querySelectorAll('.container');

const draggable = new Draggable(containers, {
  draggable: '.item',
  plugins: [Plugins.Proxy]
});

В данном случае:

  • .item — элементы, доступные для перетаскивания
  • Plugins.Proxy — плагин, создающий прокси‑элемент.

После подключения библиотека автоматически начинает использовать прокси‑объект при старте перетаскивания.


Жизненный цикл Proxy

Прокси проходит несколько стадий жизненного цикла.

1. DragStart

При событии начала перетаскивания:

  • создаётся клон элемента
  • применяются стили позиционирования
  • прокси добавляется в DOM
drag:start → proxy:create

2. DragMove

На каждом движении курсора:

  • вычисляются координаты
  • прокси перемещается
  • оригинальный элемент остаётся на месте
pointermove → proxy:updatePosition

3. DragStop

После завершения операции:

  • прокси удаляется
  • оригинальный элемент перемещается в новый контейнер
drag:stop → proxy:destroy

Создание прокси‑элемента

По умолчанию Draggable создаёт cloneNode копию элемента.

Пример структуры:

<div class="item draggable-source">...</div>

<div class="item draggable-proxy">...</div>

Отличия:

Элемент Назначение
draggable-source исходный элемент
draggable-proxy визуальный элемент перемещения

Прокси получает специальный CSS‑класс:

draggable-mirror

Этот класс используется для стилизации.


Управление позиционированием

Proxy использует absolute или fixed позиционирование.

Пример стилей:

.draggable-mirror {
  position: fixed;
  pointer-events: none;
  z-index: 9999;
}

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

  • pointer-events: none предотвращает перехват событий
  • z-index гарантирует отображение поверх интерфейса
  • position: fixed обеспечивает точное следование курсору.

Синхронизация с координатами курсора

Прокси постоянно обновляет свою позицию.

Типичная формула:

proxy.style.transform =
  `translate(${x}px, ${y}px)`;

Использование transform предпочтительнее top/left, поскольку:

  • не вызывает reflow
  • ускоряет рендеринг
  • использует GPU.

Смещение прокси относительно курсора

Иногда требуется, чтобы курсор находился не в левом верхнем углу элемента, а в точке захвата.

Для этого используется offset.

Пример:

const offsetX = event.clientX - elementRect.left;
const offsetY = event.clientY - elementRect.top;

Дальнейшее позиционирование:

proxyX = pointerX - offsetX
proxyY = pointerY - offsetY

Такой подход обеспечивает естественное ощущение перетаскивания.


Кастомизация Proxy через настройки

Proxy‑плагин поддерживает конфигурацию.

Пример:

const draggable = new Draggable(containers, {
  draggable: '.item',
  plugins: [Plugins.Proxy],
  proxy: {
    appendTo: document.body,
    constrainDimensions: true
  }
});

Параметры:

Параметр Назначение
appendTo контейнер для прокси
constrainDimensions сохранять размеры
cursorOffset смещение относительно курсора

appendTo: управление контейнером

По умолчанию прокси добавляется в body.

document.body

Это предотвращает проблемы:

  • overflow контейнеров
  • clipping
  • z-index конфликтов.

Однако можно указать другой контейнер.

proxy: {
  appendTo: document.querySelector('.drag-layer')
}

Используется в сложных интерфейсах.


constrainDimensions

Опция сохраняет размеры исходного элемента.

Без неё прокси может получить другие размеры из-за CSS.

Пример:

proxy: {
  constrainDimensions: true
}

Алгоритм:

width = source.offsetWidth
height = source.offsetHeight

Cursor Offset

Позволяет явно задать положение прокси относительно курсора.

proxy: {
  cursorOffset: {
    x: 20,
    y: 20
  }
}

Это удобно при:

  • drag‑preview
  • крупных карточках
  • drag‑иконках.

Proxy и Placeholder

При использовании прокси исходный элемент обычно заменяется placeholder‑элементом.

Placeholder:

  • занимает место в контейнере
  • предотвращает схлопывание layout.

Пример:

[Item A]
[Item B]
[Placeholder]
[Item C]

События Proxy

Плагин интегрируется в систему событий Draggable.

Основные события:

Событие Описание
drag:start начало перетаскивания
drag:move движение
drag:stop завершение

Через них можно модифицировать прокси.


Изменение Proxy в drag:start

draggable.on('drag:start', (event) => {
  const mirror = event.source;

  mirror.style.opacity = '0.8';
});

Можно:

  • изменить прозрачность
  • добавить класс
  • добавить дополнительные элементы.

Динамическая модификация Proxy

Proxy можно модифицировать во время движения.

draggable.on('drag:move', (event) => {
  const mirror = event.source;

  mirror.style.transform += ' scale(1.05)';
});

Используется для:

  • анимаций
  • визуального feedback.

Создание кастомного Proxy

Иногда требуется полностью собственный прокси‑элемент.

Пример:

draggable.on('drag:start', (event) => {
  const proxy = document.createElement('div');

  proxy.classList.add('custom-proxy');
  proxy.textContent = event.source.textContent;

  document.body.appendChild(proxy);
});

Так можно создавать:

  • карточки
  • drag‑preview
  • drag‑tooltip.

Оптимизация производительности

При работе с прокси важно учитывать:

Минимизация DOM‑операций

Лучше:

transform

Хуже:

top
left

GPU ускорение

.draggable-mirror {
  will-change: transform;
}

Ограничение перерисовки

Следует избегать:

  • тяжёлых теней
  • фильтров
  • blur.

Proxy и мобильные устройства

На сенсорных экранах прокси особенно важен.

Причины:

  • палец закрывает элемент
  • требуется offset
  • нужен увеличенный preview.

Типичная практика:

.draggable-mirror {
  transform: scale(1.1);
}

Proxy и доступность

При реализации drag‑and‑drop необходимо учитывать доступность.

Proxy должен:

  • не перехватывать фокус
  • не ломать tab‑навигацию
  • корректно скрываться для screen reader.

Иногда применяют:

aria-hidden="true"

Расширение Proxy‑плагина

Поскольку архитектура Shopify Draggable построена на системе плагинов, Proxy можно расширять.

Пример пользовательского плагина:

class CustomProxyPlugin {
  attach() {
    this.draggable.on('drag:start', this.handleStart);
  }

  detach() {
    this.draggable.off('drag:start', this.handleStart);
  }

  handleStart(event) {
    console.log('Proxy created');
  }
}

Регистрация:

new Draggable(containers, {
  draggable: '.item',
  plugins: [CustomProxyPlugin]
});

Такой подход позволяет:

  • внедрять собственную логику
  • модифицировать поведение прокси
  • строить сложные drag‑интерфейсы.

Практические сценарии использования

Proxy активно применяется в интерфейсах:

Kanban‑доски

Карточка перемещается как независимый визуальный элемент.

Drag‑preview файлов

Прокси показывает:

3 files

Конструкторы интерфейсов

Перетаскиваемый блок отображается как preview‑компонент.

Списки сортировки

Proxy предотвращает резкие скачки элементов.


Архитектурные преимущества Proxy‑подхода

Использование прокси‑элемента обеспечивает:

  • разделение данных и представления
  • контроль визуального слоя
  • устойчивость layout
  • высокую производительность
  • расширяемость через плагины.

Внутри архитектуры Shopify Draggable Proxy выступает ключевым механизмом построения гибких drag‑интерфейсов, позволяя создавать сложные визуальные сценарии перетаскивания без прямого перемещения исходных DOM‑элементов.