Il drag and drop è una delle interazioni più usate nelle web app moderne: liste riordinabili, upload file, kanban board, builder visivi. In JavaScript hai due strade — l’HTML5 Drag and Drop API nativa oppure una libreria. In questa guida le confrontiamo con codice ES2024 pronto da usare.
HTML5 Drag and Drop API — come funziona
L’API nativa del browser si basa su due ingredienti: l’attributo draggable="true" sull’elemento da trascinare e una serie di eventi da ascoltare.
Tutti gli eventi disponibili
| Evento | Si attiva su | Quando |
|---|---|---|
dragstart | elemento draggable | L’utente inizia a trascinare |
drag | elemento draggable | Durante il trascinamento (ogni pochi ms) |
dragend | elemento draggable | L’utente rilascia (successo o annullamento) |
dragenter | drop target | L’elemento trascinato entra nella zona di drop |
dragover | drop target | L’elemento è sopra la zona di drop |
dragleave | drop target | L’elemento lascia la zona di drop |
drop | drop target | L’elemento viene rilasciato |
Regola fondamentale: chiama sempre event.preventDefault() su dragover — senza di esso il browser non attiverà mai l’evento drop.
Implementazione base — ES2024
<div class="draggable" draggable="true" id="item-1">Trascinami</div>
<div class="dropzone" id="zone-1">Rilascia qui</div>const draggable = document.querySelector('.draggable');
const dropzone = document.querySelector('.dropzone');
// --- Elemento trascinabile ---
draggable.addEventListener('dragstart', ({ dataTransfer, target }) => {
dataTransfer.setData('text/plain', target.id);
dataTransfer.effectAllowed = 'move';
target.style.opacity = '0.4';
});
draggable.addEventListener('dragend', ({ target }) => {
target.style.opacity = '1';
});
// --- Zona di rilascio ---
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
dropzone.classList.add('active');
});
dropzone.addEventListener('dragleave', () => {
dropzone.classList.remove('active');
});
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
dropzone.classList.remove('active');
const id = e.dataTransfer.getData('text/plain');
const el = document.getElementById(id);
dropzone.appendChild(el);
});Caso pratico: lista riordinabile
Il pattern più comune — riordinare elementi in una lista trascinandoli.
<ul id="sortable-list">
<li draggable="true">Task 1</li>
<li draggable="true">Task 2</li>
<li draggable="true">Task 3</li>
<li draggable="true">Task 4</li>
</ul>const list = document.getElementById('sortable-list');
let draggedItem = null;
list.addEventListener('dragstart', ({ target }) => {
draggedItem = target;
target.classList.add('dragging');
});
list.addEventListener('dragend', ({ target }) => {
target.classList.remove('dragging');
draggedItem = null;
});
list.addEventListener('dragover', (e) => {
e.preventDefault();
const afterEl = getDragAfterElement(list, e.clientY);
if (afterEl) {
list.insertBefore(draggedItem, afterEl);
} else {
list.appendChild(draggedItem);
}
});
// Trova l'elemento sotto il cursore per inserire nel punto corretto
function getDragAfterElement(container, y) {
const draggableEls = [...container.querySelectorAll('[draggable]:not(.dragging)')];
return draggableEls.reduce((closest, child) => {
const { top, height } = child.getBoundingClientRect();
const offset = y - top - height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset, element: child };
}
return closest;
}, { offset: Number.NEGATIVE_INFINITY }).element;
}Caso pratico: upload file con drag & drop
L’API nativa gestisce anche il trascinamento di file dal filesystem del sistema operativo — senza bisogno di librerie.
<div id="file-drop" class="dropzone">
Trascina i file qui oppure <label>scegli dal computer<input type="file" hidden multiple /></label>
</div>
<ul id="file-list"></ul>const fileDropZone = document.getElementById('file-drop');
const fileList = document.getElementById('file-list');
fileDropZone.addEventListener('dragover', (e) => {
e.preventDefault();
fileDropZone.classList.add('active');
});
fileDropZone.addEventListener('dragleave', () => {
fileDropZone.classList.remove('active');
});
fileDropZone.addEventListener('drop', (e) => {
e.preventDefault();
fileDropZone.classList.remove('active');
const files = [...e.dataTransfer.files];
files.forEach(({ name, size, type }) => {
const li = document.createElement('li');
li.textContent = `${name} — ${(size / 1024).toFixed(1)} KB — ${type || 'tipo sconosciuto'}`;
fileList.appendChild(li);
});
});HTML5 nativo vs librerie — confronto
| Criterio | HTML5 API nativa | SortableJS | dnd-kit (React) | react-beautiful-dnd |
|---|---|---|---|---|
| Peso aggiuntivo | 0 KB | ~15 KB | ~30 KB | ~30 KB |
| Touch / mobile | No (bug storici) | Sì | Sì | Sì |
| Accessibilità | Manuale | Parziale | Eccellente | Eccellente |
| Animazioni | Manuali con CSS | Incluse | Configurabili | Fluide out-of-the-box |
| Liste riordinabili | Codice custom | Nativo | Nativo | Nativo |
| Framework | Vanilla JS | Vanilla / Vue / React | Solo React | Solo React |
| Manutenzione | Stabile (web standard) | Attiva | Attiva | Deprecata (usa dnd-kit) |
Quando usare l’API nativa
Scegli l’API HTML5 nativa quando: hai un caso d’uso semplice (una dropzone, upload file), non hai utenti mobile, vuoi zero dipendenze e controllo totale.
Quando usare una libreria
SortableJS — la scelta migliore per vanilla JS. Supporta touch, animazioni, gruppi di liste, integrazione con Vue e React. Zero configurazione per il caso base:
import Sortable from 'sortablejs';
const el = document.getElementById('sortable-list');
const sortable = new Sortable(el, {
animation: 150,
ghostClass: 'sortable-ghost',
onEnd({ oldIndex, newIndex }) {
console.log(`Spostato da ${oldIndex} a ${newIndex}`);
},
});dnd-kit — la scelta per React moderno. Accessibilità integrata, supporto touch, altamente componibile:
// npm install @dnd-kit/core @dnd-kit/sortable
import { DndContext } from '@dnd-kit/core';
import { SortableContext, useSortable } from '@dnd-kit/sortable';
// Ogni item usa useSortable() per ottenere gli attributi drag
function SortableItem({ id }) {
const { attributes, listeners, setNodeRef, transform } = useSortable({ id });
return (
<div ref={setNodeRef} {...attributes} {...listeners}>
{id}
</div>
);
}Il limite principale: touch e mobile
L’API HTML5 nativa non funziona su dispositivi touch (iOS e Android). Gli eventi dragstart e drop non vengono emessi al tocco. Se hai utenti mobile, hai tre opzioni:
- Usa SortableJS — gestisce touch nativamente
- Usa dnd-kit in React — pointer events unificati
- Polyfill DragDropTouch — mappa touch events su drag events (soluzione leggera per progetti vanilla)
Errori comuni e fix rapidi
| Errore | Causa | Fix |
|---|---|---|
L’evento drop non si attiva mai | Manca e.preventDefault() su dragover | Aggiungilo sempre su dragover |
| Scrollbar orizzontale durante slide | overflow-x: hidden sul body | Mettilo sul wrapper, non sul body |
| Non funziona su mobile | API nativa non supporta touch | Usa SortableJS o dnd-kit |
| Elemento rimane invisibile dopo drop | Opacity non ripristinata su dragend | Aggiungi listener dragend che reimposta gli stili |
Hai trovato utile questo articolo? Seguimi su @cyberalchimista per altri tutorial e consigli sullo sviluppo web. Per domande o progetti, contattami.

