Drag and Drop con JavaScript
Impara il drag and drop in JavaScript: approccio con eventi mouse e API HTML5 nativa con draggable, dragover, drop e DataTransfer, con esempio pratico.
In questo tutorial esploreremo la funzionalità drag-and-drop in JavaScript, una potente funzione che arricchisce l'interattività delle pagine web. Esistono due modi per implementarla: l'approccio con eventi mouse (mousedown/mousemove/mouseup), che offre il controllo totale sul movimento, e la API HTML5 nativa Drag and Drop (draggable + gli eventi drag*), integrata nel browser. Tratteremo entrambe, vedremo dove ciascuna si adatta meglio, poi realizzeremo un esempio pratico in cui un'icona di lampadina illumina un'area scura quando viene trascinata all'interno.
Cos'è il Drag-and-Drop?
Il drag-and-drop è un'interazione dell'interfaccia utente che consente agli utenti di afferrare un oggetto e spostarlo in una posizione diversa sullo schermo. Lo si trova ovunque: trascinare file nel sistema operativo, riordinare elementi in un gioco, caricare foto rilasciandole su una pagina, oppure riordinare una lista di cose da fare. Il pattern prevede sempre tre ruoli:
- Un elemento draggable — l'elemento che l'utente prende.
- Un target di rilascio (o droppable) — l'area che può riceverlo.
- Un payload — dati opzionali trasportati dalla sorgente al target (ad esempio, l'id dell'elemento che viene spostato).
Scegliere un Approccio
Entrambe le tecniche sono valide; scegli in base alle tue esigenze.
- Eventi mouse (
mousedown,mousemove,mouseup) — tieni traccia manualmente del puntatore e sposti l'elemento tu stesso. Usa questo approccio quando hai bisogno che l'elemento segua il cursore pixel per pixel (slider, canvas a forma libera, strumenti di disegno, fisica personalizzata). È anche l'approccio da estendere ai touchscreen contouchstart/touchmove/touchend. - API HTML5 nativa Drag and Drop (
draggable="true"+dragstart/dragover/drop) — il browser gestisce l'immagine "fantasma" e il gesto di trascinamento per te, e ti fornisce un oggettoDataTransferper trasportare dati. Usala per trasferire qualcosa tra zone (caricamento file, riordinamento liste, rilascio di elementi in un carrello). Non funziona di default sui dispositivi touch.
Dimostreremo l'approccio con eventi mouse nell'esempio principale, poi riassumeremo la API nativa.
Concetti Fondamentali del Drag-and-Drop in JavaScript
L'Algoritmo del Drag'n'Drop
- Avvio del Trascinamento:
- Il processo inizia quando l'utente clicca sull'elemento e tiene premuto il pulsante del mouse.
- Trascinamento dell'Elemento:
- Man mano che il mouse si sposta, l'elemento segue il percorso del cursore sullo schermo.
- Rilascio dell'Elemento:
- L'elemento viene rilasciato quando l'utente lascia andare il pulsante del mouse, posizionando l'elemento in una nuova posizione.
Capire i Droppable
I droppable sono aree designate a ricevere gli elementi draggable. Queste aree rilevano quando un oggetto draggable è sopra di esse e possono attivare azioni specifiche in risposta.
Assicurati che la funzionalità drag-and-drop sia compatibile con il touch. Gli utenti mobile devono poter trascinare e rilasciare con i gesti touch. Considera l'implementazione degli eventi touch (touchstart, touchmove, touchend) o l'utilizzo di una libreria leggera per la compatibilità cross-device.
Esempio Interattivo: Area Chiara e Scura
Mettiamo in pratica la teoria con un esempio semplice ma interattivo. Useremo un'icona di lampadina come oggetto draggable. Quando questa icona viene spostata su un'area scura, l'area si illuminerà, simulando l'effetto di una luce accesa.
Configurare HTML e CSS
Prima definiamo la struttura e lo stile di base. Includiamo un box scuro e un'icona di lampadina.
Struttura HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Interactive Lighting with Drag and Drop</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" />
<style>
#darkArea {
width: 300px;
height: 300px;
background-color: #333;
position: relative;
margin-top: 20px;
}
#lightIcon {
font-size: 48px;
color: #ccc;
cursor: pointer;
position: absolute;
}
</style>
</head>
<body>
<div id="main">
<div id="darkArea"></div>
<i id="lightIcon" class="fas fa-lightbulb"></i>
</div>
<script>
// JavaScript will be added here.
</script>
</body>
</html>Implementare il JavaScript
Ora aggiungiamo la funzionalità per rendere la lampadina draggable e reattiva all'area scura.
Spiegazione del Codice JavaScript
<script>
// Get references to the light bulb icon and the dark area on the webpage
var lightIcon = document.getElementById("lightIcon");
var darkArea = document.getElementById("darkArea");
// Variables to track whether the dragging is active and to store position data
var active = false;
var initialX, initialY, currentX, currentY, xOffset = 0, yOffset = 0;
// Listen for the mouse down event on the light bulb icon
lightIcon.addEventListener("mousedown", function(e) {
// Record the starting position of the mouse and adjust by any existing offset
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
// Set the active flag to true, indicating that dragging has started
active = true;
});
// Listen for mouse movement across the entire document
document.addEventListener("mousemove", function(e) {
// If not dragging, don't do anything
if (!active) return;
// Calculate the new position of the mouse
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
// Update the offset with the new position
xOffset = currentX;
yOffset = currentY;
// Move the light bulb icon to the new position
lightIcon.style.transform = "translate3d(" + currentX + "px, " + currentY + "px, 0)";
});
// Listen for the mouse up event across the entire document
document.addEventListener("mouseup", function() {
// Save the final position of the light bulb
initialX = currentX;
initialY = currentY;
// Set the active flag to false, indicating dragging has ended
active = false;
// Check if the light bulb is inside the dark area
if (isInside(darkArea, lightIcon)) {
// Change the background color of the dark area to yellow
darkArea.style.backgroundColor = "yellow";
// Change the color of the light bulb to yellow
lightIcon.style.color = "yellow";
} else {
// Revert the dark area's color to dark
darkArea.style.backgroundColor = "#333";
// Revert the light bulb's color to gray
lightIcon.style.color = "#ccc";
}
});
// Function to check if the light bulb is inside the dark area
function isInside(container, element) {
// Get the position of the container and the element
var containerRect = container.getBoundingClientRect();
var elementRect = element.getBoundingClientRect();
// Return true if the element is within the container's boundaries
return (
elementRect.left >= containerRect.left &&
elementRect.right <= containerRect.right &&
elementRect.top >= containerRect.top &&
elementRect.bottom <= containerRect.bottom
);
}
</script>Questo script rende la lampadina draggable con il mouse e reagisce quando la rilasci sull'area scura. Ecco cosa fa ciascuna parte:
- Configurazione: Lo script ottiene i riferimenti all'icona della lampadina e all'area scura, e dichiara le variabili usate per tracciare il trascinamento (
active, la posizione iniziale del mouse, l'offset corrente). - Avvio del trascinamento (
mousedown): Quando premi il pulsante del mouse sulla lampadina, lo script registra la posizione iniziale del cursore (meno qualsiasi offset da un trascinamento precedente) e impostaactive = true. L'offset è importante — senza di esso, l'icona tornerebbe all'origine ogni volta che avvii un nuovo trascinamento. - Movimento (
mousemove): Finchéactiveètrue, lo script calcola quanto si è spostato il cursore e lo applica come trasformazionetranslate3d, così l'icona segue il puntatore. Seactiveèfalse, il gestore ritorna immediatamente senza fare nulla. - Rilascio (
mouseup): Quando rilasci il pulsante, lo script salva la posizione finale, impostaactive = falsee chiamaisInsideper determinare se la lampadina è atterrata nell'area scura. In caso affermativo, sia l'area che la lampadina diventano gialle; altrimenti tornano ai colori predefiniti. - Hit testing (
isInside): Questo helper confronta i rettangoli delimitatori dei due elementi congetBoundingClientRect()e restituiscetruesolo quando la lampadina è completamente contenuta all'interno dei bordi dell'area scura.
Usa translate3d nelle trasformazioni CSS per trascinare elementi. Sfrutta l'accelerazione GPU, portando a movimenti più fluidi e minore carico CPU, fondamentale per applicazioni ad alta intensità di prestazioni.
Esempio Completo
Ora è il momento di vederlo tutto in azione:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Interactive Lighting with Drag and Drop</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" />
<style>
#darkArea {
width: 300px;
height: 300px;
background-color: #333;
position: relative;
margin-top: 20px;
}
#lightIcon {
font-size: 48px;
color: #ccc;
cursor: pointer;
position: absolute;
}
</style>
</head>
<body>
<div id="main">
<p>Move the light into the dark area to light it up!</p>
<div id="darkArea"></div>
<i id="lightIcon" class="fas fa-lightbulb"></i>
</div>
<script>
// Get references to the light bulb icon and the dark area on the webpage
var lightIcon = document.getElementById("lightIcon");
var darkArea = document.getElementById("darkArea");
// Variables to track whether the dragging is active and to store position data
var active = false;
var initialX,
initialY,
currentX,
currentY,
xOffset = 0,
yOffset = 0;
// Listen for the mouse down event on the light bulb icon
lightIcon.addEventListener("mousedown", function (e) {
// Record the starting position of the mouse and adjust by any existing offset
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
// Set the active flag to true, indicating that dragging has started
active = true;
});
// Listen for mouse movement across the entire document
document.addEventListener("mousemove", function (e) {
// If not dragging, don't do anything
if (!active) return;
// Calculate the new position of the mouse
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
// Update the offset with the new position
xOffset = currentX;
yOffset = currentY;
// Move the light bulb icon to the new position
lightIcon.style.transform = "translate3d(" + currentX + "px, " + currentY + "px, 0)";
});
// Listen for the mouse up event across the entire document
document.addEventListener("mouseup", function () {
// Save the final position of the light bulb
initialX = currentX;
initialY = currentY;
// Set the active flag to false, indicating dragging has ended
active = false;
// Check if the light bulb is inside the dark area
if (isInside(darkArea, lightIcon)) {
// Change the background color of the dark area to yellow
darkArea.style.backgroundColor = "yellow";
lightIcon.style.color = "yellow";
} else {
// Revert the dark area's color to dark
darkArea.style.backgroundColor = "#333";
// Revert the light bulb's color to gray
lightIcon.style.color = "#ccc";
}
});
// Function to check if the light bulb is inside the dark area
function isInside(container, element) {
// Get the position of the container and the element
var containerRect = container.getBoundingClientRect();
var elementRect = element.getBoundingClientRect();
// Return true if the element is within the container's boundaries
return elementRect.left >= containerRect.left && elementRect.right <= containerRect.right && elementRect.top >= containerRect.top && elementRect.bottom <= containerRect.bottom;
}
</script>
</body>
</html>Principali Eventi Mouse Utilizzati:
mousedown: Questo evento viene attivato quando l'utente preme il pulsante del mouse sull'icona della lampadina. Segna l'inizio del trascinamento e registra la posizione iniziale del cursore.mousemove: Questo evento si attiva quando il mouse viene spostato. Se il trascinamento è attivo (ossia il pulsante del mouse è ancora premuto), calcola la nuova posizione dell'icona in base al movimento del cursore e aggiorna la posizione della lampadina sullo schermo.mouseup: Questo evento si verifica quando l'utente rilascia il pulsante del mouse, segnando la fine del trascinamento. Controlla se la lampadina si trova all'interno dei bordi dell'area scura per decidere se cambiare il colore di sfondo dell'area.
Come abbiamo appreso nell'articolo sugli Eventi Mouse, questi eventi sono fondamentali per creare la funzionalità drag-and-drop interattiva, consentendo agli elementi di una pagina web di essere spostati dinamicamente e interagiti con il mouse. (Se desideri posizionare l'elemento con precisione, il capitolo sulle Coordinate JavaScript spiega la differenza tra clientX/clientY, pageX/pageY e getBoundingClientRect().)
La API HTML5 Nativa Drag and Drop
La tecnica con eventi mouse è ottima quando hai bisogno che un elemento segua esattamente il cursore. Ma quando il tuo obiettivo reale è trasferire qualcosa — rilasciare una card in una colonna, un elemento in un carrello, un file in una zona di upload — la API Drag and Drop integrata nel browser richiede meno codice e gestisce il gesto di trascinamento per te.
Rendere un Elemento Draggable
Qualsiasi elemento diventa draggable impostando l'attributo draggable su true. I link e le immagini sono draggable per impostazione predefinita; tutto il resto non lo è.
<div id="item" draggable="true">Drag me</div>
<div id="dropzone">Drop here</div>Gli Eventi Drag and Drop
La API attiva una sequenza di eventi. I più importanti sono:
| Evento | Si attiva su | Quando |
|---|---|---|
dragstart | l'elemento trascinato | nel momento in cui il trascinamento inizia |
dragover | il target di rilascio | continuamente mentre un draggable è sopra di esso |
drop | il target di rilascio | quando l'utente rilascia sopra di esso |
dragend | l'elemento trascinato | quando il trascinamento termina (rilasciato o annullato) |
Trasportare Dati con DataTransfer
Ogni evento drag espone event.dataTransfer, un oggetto usato per allegare un payload in dragstart e leggerlo di nuovo in drop.
const item = document.getElementById("item");
const zone = document.getElementById("dropzone");
// 1. Attach a payload when the drag starts.
item.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text/plain", item.id);
});
// 2. By default elements are NOT valid drop targets.
// Prevent the default to allow a drop.
zone.addEventListener("dragover", (e) => {
e.preventDefault();
});
// 3. Read the payload and move the element on drop.
zone.addEventListener("drop", (e) => {
e.preventDefault();
const id = e.dataTransfer.getData("text/plain");
zone.appendChild(document.getElementById(id));
});L'errore più comune con la API nativa è dimenticare e.preventDefault() all'interno del gestore dragover. Senza di esso, il browser rifiuta l'elemento come target di rilascio e l'evento drop non si attiva mai. I dati impostati con setData sono disponibili di nuovo solo quando viene eseguito drop — non possono essere letti durante dragover per motivi di sicurezza.
La coppia setData(format, value) / getData(format) consente di trasportare testo semplice, URL (text/uri-list), HTML, o le proprie chiavi string personalizzate. Per i caricamenti di file, leggi e.dataTransfer.files, che è un FileList proprio come un <input type="file">.
Eventi Mouse vs. API Nativa in Sintesi
- Usa gli eventi mouse quando il movimento deve essere libero e accurato al pixel, oppure quando hai bisogno del supporto touch.
- Usa la API nativa quando stai spostando un elemento discreto da un posto all'altro e vuoi che il browser gestisca l'immagine di trascinamento e il gesto.
Per approfondire il sistema di eventi sottostante, consulta Introduzione agli Eventi del Browser, e per le interazioni accessibili ricorda che il drag-and-drop dovrebbe sempre avere un'alternativa da tastiera — consulta Considerazioni sull'Accessibilità del DOM.
Conclusione
Il drag-and-drop rende le interfacce dirette e intuitive. Ora hai due strumenti a disposizione: l'approccio con eventi mouse, che sposta un elemento pixel per pixel sotto pieno controllo manuale, e la API HTML5 nativa Drag and Drop, che lascia al browser la gestione del gesto mentre tu trasporti i dati tramite DataTransfer. Scegli gli eventi mouse per il movimento libero e il supporto touch, e la API nativa per trasferire elementi tra zone. Qualunque cosa tu scelga, fornisci un'alternativa accessibile da tastiera affinché ogni utente possa completare la stessa operazione.