Библиотека Shopify Draggable реализует перетаскивание элементов через систему модульных плагинов. Одним из ключевых механизмов визуального взаимодействия является Proxy‑паттерн, позволяющий отображать во время перетаскивания не сам исходный элемент, а его специальную копию — прокси‑элемент.
Прокси используется для:
Во время drag‑операции создаётся виртуальное представление элемента, которое следует за курсором, тогда как исходный элемент может:
Такая архитектура обеспечивает чистоту DOM, высокую производительность и гибкость кастомизации.
Proxy‑модуль в Draggable реализует промежуточный слой между:
source)proxy)Схема взаимодействия:
DragStart
↓
Создание Proxy
↓
Следование Proxy за курсором
↓
Drop
↓
Удаление Proxy
Преимущества подхода:
1. Отделение логики от представления
Оригинальный элемент остаётся в контейнере и не требует перемещения по DOM.
2. Управление производительностью
Перемещение одного прокси‑элемента дешевле, чем сложные операции с множеством контейнеров.
3. Гибкость визуализации
Прокси может:
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 — плагин, создающий прокси‑элемент.После подключения библиотека автоматически начинает использовать прокси‑объект при старте перетаскивания.
Прокси проходит несколько стадий жизненного цикла.
При событии начала перетаскивания:
drag:start → proxy:create
На каждом движении курсора:
pointermove → proxy:updatePosition
После завершения операции:
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, поскольку:
Иногда требуется, чтобы курсор находился не в левом верхнем углу элемента, а в точке захвата.
Для этого используется offset.
Пример:
const offsetX = event.clientX - elementRect.left;
const offsetY = event.clientY - elementRect.top;
Дальнейшее позиционирование:
proxyX = pointerX - offsetX
proxyY = pointerY - offsetY
Такой подход обеспечивает естественное ощущение перетаскивания.
Proxy‑плагин поддерживает конфигурацию.
Пример:
const draggable = new Draggable(containers, {
draggable: '.item',
plugins: [Plugins.Proxy],
proxy: {
appendTo: document.body,
constrainDimensions: true
}
});
Параметры:
| Параметр | Назначение |
|---|---|
| appendTo | контейнер для прокси |
| constrainDimensions | сохранять размеры |
| cursorOffset | смещение относительно курсора |
По умолчанию прокси добавляется в body.
document.body
Это предотвращает проблемы:
Однако можно указать другой контейнер.
proxy: {
appendTo: document.querySelector('.drag-layer')
}
Используется в сложных интерфейсах.
Опция сохраняет размеры исходного элемента.
Без неё прокси может получить другие размеры из-за CSS.
Пример:
proxy: {
constrainDimensions: true
}
Алгоритм:
width = source.offsetWidth
height = source.offsetHeight
Позволяет явно задать положение прокси относительно курсора.
proxy: {
cursorOffset: {
x: 20,
y: 20
}
}
Это удобно при:
При использовании прокси исходный элемент обычно заменяется placeholder‑элементом.
Placeholder:
Пример:
[Item A]
[Item B]
[Placeholder]
[Item C]
Плагин интегрируется в систему событий Draggable.
Основные события:
| Событие | Описание |
|---|---|
| drag:start | начало перетаскивания |
| drag:move | движение |
| drag:stop | завершение |
Через них можно модифицировать прокси.
draggable.on('drag:start', (event) => {
const mirror = event.source;
mirror.style.opacity = '0.8';
});
Можно:
Proxy можно модифицировать во время движения.
draggable.on('drag:move', (event) => {
const mirror = event.source;
mirror.style.transform += ' scale(1.05)';
});
Используется для:
Иногда требуется полностью собственный прокси‑элемент.
Пример:
draggable.on('drag:start', (event) => {
const proxy = document.createElement('div');
proxy.classList.add('custom-proxy');
proxy.textContent = event.source.textContent;
document.body.appendChild(proxy);
});
Так можно создавать:
При работе с прокси важно учитывать:
Лучше:
transform
Хуже:
top
left
.draggable-mirror {
will-change: transform;
}
Следует избегать:
На сенсорных экранах прокси особенно важен.
Причины:
Типичная практика:
.draggable-mirror {
transform: scale(1.1);
}
При реализации drag‑and‑drop необходимо учитывать доступность.
Proxy должен:
Иногда применяют:
aria-hidden="true"
Поскольку архитектура 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]
});
Такой подход позволяет:
Proxy активно применяется в интерфейсах:
Карточка перемещается как независимый визуальный элемент.
Прокси показывает:
3 files
Перетаскиваемый блок отображается как preview‑компонент.
Proxy предотвращает резкие скачки элементов.
Использование прокси‑элемента обеспечивает:
Внутри архитектуры Shopify Draggable Proxy выступает ключевым механизмом построения гибких drag‑интерфейсов, позволяя создавать сложные визуальные сценарии перетаскивания без прямого перемещения исходных DOM‑элементов.