Кастомный Drag and Drop
Кастомный Drag and Drop — реализация перетаскивания элементов средствами JavaScript через события
mousedown/mousemove/mouseup(или Pointer Events API) с вычислением смещений и управлением позицией через CSS.
Зачем нужно
Встроенный HTML5 Drag and Drop API (draggable, dragstart, drop) имеет ограничения в кастомизации внешнего вида, плохо работает на мобильных устройствах и не всегда предсказуем. Кастомная реализация через mouse/pointer events даёт полный контроль над поведением, анимациями и поддержкой тач-экранов.
Где используется
- Kanban-доски и todo-листы с перетаскиванием задач
- Drag-and-drop загрузка файлов (кастомная зона)
- Перестановка элементов в списке
- Изменение размера панелей (resizable panels)
Реализация: перетаскиваемый элемент
function makeDraggable(element) {
let startX, startY, startLeft, startTop;
let isDragging = false;
element.style.position = 'absolute';
function onMouseMove(e) {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
element.style.left = `${startLeft + dx}px`;
element.style.top = `${startTop + dy}px`;
}
function onMouseUp() {
isDragging = false;
element.classList.remove('dragging');
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
}
element.addEventListener('mousedown', (e) => {
e.preventDefault(); // запрещаем выделение текста
isDragging = true;
startX = e.clientX;
startY = e.clientY;
startLeft = element.offsetLeft;
startTop = element.offsetTop;
element.classList.add('dragging');
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
}
makeDraggable(document.getElementById('card'));
Реализация: сортируемый список
function makeSortable(list) {
let dragged = null;
list.addEventListener('dragstart', (e) => {
dragged = e.target.closest('li');
dragged.classList.add('dragging');
});
list.addEventListener('dragend', () => {
dragged?.classList.remove('dragging');
dragged = null;
});
list.addEventListener('dragover', (e) => {
e.preventDefault(); // разрешить drop
const target = e.target.closest('li');
if (!target || target === dragged) return;
const rect = target.getBoundingClientRect();
const midY = rect.top + rect.height / 2;
if (e.clientY < midY) {
list.insertBefore(dragged, target);
} else {
list.insertBefore(dragged, target.nextSibling);
}
});
// Нужно добавить draggable="true" в HTML
list.querySelectorAll('li').forEach(li => {
li.setAttribute('draggable', 'true');
});
}
makeSortable(document.querySelector('.todo-list'));
Pointer Events API (рекомендуется для тач-устройств)
function makeDraggablePointer(el) {
let startX, startY, startLeft, startTop;
el.style.position = 'absolute';
el.style.touchAction = 'none'; // отключить scroll
el.addEventListener('pointerdown', (e) => {
startX = e.clientX;
startY = e.clientY;
startLeft = el.offsetLeft;
startTop = el.offsetTop;
el.setPointerCapture(e.pointerId); // события приходят на элемент
});
el.addEventListener('pointermove', (e) => {
if (!e.buttons) return;
el.style.left = `${startLeft + e.clientX - startX}px`;
el.style.top = `${startTop + e.clientY - startY}px`;
});
}
Частые ошибки
- Не слушать события на
document—mousemoveнужно слушать наdocument, а не на элементе; если мышь выйдет за пределы,mousemoveперестанет приходить. - Не вызывать
preventDefaultвdragover— без этого браузер не разрешит drop, и событиеdropне сработает. - Игнорировать мобильные устройства — мышиные события не работают на тач; используйте Pointer Events или добавляйте поддержку touch-событий.
Связанные темы
- _MOC DOM
- _MOC JavaScript
- getBoundingClientRect -- размеры и позиция
- style -- инлайн-стили через JS
- preventDefault и stopPropagation