Drag and Drop JavaScript: guida completa con HTML5 API nativa e librerie

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

EventoSi attiva suQuando
dragstartelemento draggableL’utente inizia a trascinare
dragelemento draggableDurante il trascinamento (ogni pochi ms)
dragendelemento draggableL’utente rilascia (successo o annullamento)
dragenterdrop targetL’elemento trascinato entra nella zona di drop
dragoverdrop targetL’elemento è sopra la zona di drop
dragleavedrop targetL’elemento lascia la zona di drop
dropdrop targetL’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

CriterioHTML5 API nativaSortableJSdnd-kit (React)react-beautiful-dnd
Peso aggiuntivo0 KB~15 KB~30 KB~30 KB
Touch / mobileNo (bug storici)
AccessibilitàManualeParzialeEccellenteEccellente
AnimazioniManuali con CSSIncluseConfigurabiliFluide out-of-the-box
Liste riordinabiliCodice customNativoNativoNativo
FrameworkVanilla JSVanilla / Vue / ReactSolo ReactSolo React
ManutenzioneStabile (web standard)AttivaAttivaDeprecata (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:

  1. Usa SortableJS — gestisce touch nativamente
  2. Usa dnd-kit in React — pointer events unificati
  3. Polyfill DragDropTouch — mappa touch events su drag events (soluzione leggera per progetti vanilla)

Errori comuni e fix rapidi

ErroreCausaFix
L’evento drop non si attiva maiManca e.preventDefault() su dragoverAggiungilo sempre su dragover
Scrollbar orizzontale durante slideoverflow-x: hidden sul bodyMettilo sul wrapper, non sul body
Non funziona su mobileAPI nativa non supporta touchUsa SortableJS o dnd-kit
Elemento rimane invisibile dopo dropOpacity non ripristinata su dragendAggiungi 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.

Condividi

Articoli Recenti

Categorie popolari