Drag and Drop API
Drag and Drop API — нативный браузерный API для реализации перетаскивания элементов между зонами на странице или из файловой системы в браузер, основанный на событиях
dragstart,dragover,dropи объектеDataTransfer.
Зачем нужно
Нативный Drag and Drop API доступен без сторонних библиотек и обеспечивает встроенную поддержку перетаскивания файлов из ОС в браузер. Понимание его событийной модели и объекта DataTransfer необходимо для реализации Kanban-досок, сортируемых списков, загрузки файлов через drag, а также для работы со сторонними drag-библиотеками (react-dnd, dnd-kit), которые строятся поверх него.
Где используется
- Kanban-доски: перемещение карточек между колонками
- Файловая загрузка:
dragфайлов из Explorer/Finder в зону загрузки - Сортируемые списки: изменение порядка элементов
- Конструкторы интерфейсов: перетаскивание виджетов на холст
- Ячейки таблиц: перемещение строк
Основной контент
События Drag and Drop
Исходный элемент: dragstart → drag → dragend
Целевой элемент: dragenter → dragover → dragleave/drop
Базовый пример: перемещение карточки
<div class="column" id="todo">
<div class="card" draggable="true" data-id="1">Задача 1</div>
<div class="card" draggable="true" data-id="2">Задача 2</div>
</div>
<div class="column" id="done"></div>
let draggedElement = null;
// На перетаскиваемых элементах
document.querySelectorAll('.card').forEach(card => {
card.addEventListener('dragstart', (e) => {
draggedElement = card;
e.dataTransfer.effectAllowed = 'move';
// Передаём данные — доступны в зоне drop
e.dataTransfer.setData('text/plain', card.dataset.id);
card.classList.add('dragging');
});
card.addEventListener('dragend', () => {
card.classList.remove('dragging');
draggedElement = null;
});
});
// На зонах-целях
document.querySelectorAll('.column').forEach(column => {
// dragover нужен для разрешения drop — без preventDefault drop не сработает!
column.addEventListener('dragover', (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
column.classList.add('drag-over');
});
column.addEventListener('dragleave', () => {
column.classList.remove('drag-over');
});
column.addEventListener('drop', (e) => {
e.preventDefault();
column.classList.remove('drag-over');
const id = e.dataTransfer.getData('text/plain');
const card = document.querySelector(`[data-id="${id}"]`);
if (card && card !== column) {
column.appendChild(card);
}
});
});
Загрузка файлов через drag
const dropZone = document.getElementById('drop-zone');
dropZone.addEventListener('dragover', (e) => {
e.preventDefault(); // разрешаем drop
e.dataTransfer.dropEffect = 'copy';
dropZone.classList.add('drag-over');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('drag-over');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('drag-over');
const files = [...e.dataTransfer.files]; // FileList → Array
files.forEach(async (file) => {
console.log('Файл:', file.name, file.type, file.size);
if (file.type.startsWith('image/')) {
const url = URL.createObjectURL(file);
const img = document.createElement('img');
img.src = url;
dropZone.appendChild(img);
}
// Загрузка на сервер
const formData = new FormData();
formData.append('file', file);
await fetch('/api/upload', { method: 'POST', body: formData });
});
});
DataTransfer — передача данных
// dragstart — записываем данные
e.dataTransfer.setData('text/plain', 'Простая строка');
e.dataTransfer.setData('application/json', JSON.stringify({ id: 1, type: 'card' }));
// drop — читаем данные
const text = e.dataTransfer.getData('text/plain');
const data = JSON.parse(e.dataTransfer.getData('application/json'));
// Типы MIME доступных данных
console.log([...e.dataTransfer.types]); // ['text/plain', 'application/json', ...]
// Файлы (только при перетаскивании из ОС)
const files = [...e.dataTransfer.files];
// Эффект перетаскивания (визуальный курсор)
e.dataTransfer.effectAllowed = 'move'; // или 'copy', 'link', 'copyMove', 'all'
e.dataTransfer.dropEffect = 'move'; // в dragover
Частые ошибки
- Забыть
e.preventDefault()вdragover: без негоdropне сработает — браузер блокирует drop по умолчанию. draggable="true"у всех вложенных элементов: атрибут нужен только на корневом перетаскиваемом элементе; у дочерних он создаёт конфликты.DataTransferвне обработчика событий: объектe.dataTransferдоступен только синхронно внутри обработчикаdrop— асинхронное чтение черезawaitдаст пустые данные.- iOS не поддерживает нативный D&D: для мобильных устройств нужны
touch-события или специальные библиотеки (dnd-kit поддерживает touch).