API HTML5 Drag and Drop

Questo post spiega le nozioni di base del trascinamento.

Nella maggior parte dei browser, le selezioni di testo, le immagini e i link sono trascinabili per impostazione predefinita. Ad esempio, se trascini un link in una pagina web, vedrai una piccola casella con un titolo e un URL che puoi rilasciare nella barra degli indirizzi o sul desktop per creare una scorciatoia o passare al link. Per rendere trascinabili altri tipi di contenuti, devi utilizzare le API Drag and Drop HTML5.

Per rendere un oggetto trascinabile, imposta draggable=true su quell'elemento. Quasi tutti gli elementi possono essere trascinati, tra cui immagini, file, link, file o qualsiasi markup nella pagina.

L'esempio seguente crea un'interfaccia per riorganizzare le colonne disposte con CSS Grid. Il markup di base per le colonne ha questo aspetto, con l'attributo draggable per ogni colonna impostato su true:

<div class="container">
  <div draggable="true" class="box">A</div>
  <div draggable="true" class="box">B</div>
  <div draggable="true" class="box">C</div>
</div>

Ecco il codice CSS per gli elementi contenitore e riquadro. L'unico CSS correlato alla funzionalità di trascinamento è la proprietà cursor: move. Il resto del codice controlla il layout e lo stile degli elementi del contenitore e della casella.

.container {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 10px;
}

.box {
  border: 3px solid #666;
  background-color: #ddd;
  border-radius: .5em;
  padding: 10px;
  cursor: move;
}

A questo punto puoi trascinare gli elementi, ma non succede altro. Per aggiungere un comportamento, devi utilizzare l'API JavaScript.

Ascolta gli eventi di trascinamento

Per monitorare il processo di trascinamento, puoi ascoltare uno dei seguenti eventi:

Per gestire il flusso di trascinamento, devi avere un qualche tipo di elemento di origine (da dove inizia il trascinamento), il payload di dati (l'elemento trascinato) e un target (un'area in cui avviene il rilascio). L'elemento source può essere di quasi qualsiasi tipo. Il target è la zona di rilascio o l'insieme di zone di rilascio che accettano i dati che l'utente sta tentando di inserire. Non tutti gli elementi possono essere target. Ad esempio, il target non può essere un'immagine.

Avviare e terminare una sequenza di trascinamento

Dopo aver definito gli attributi draggable="true" nei contenuti, associa un gestore di eventi dragstart per avviare la sequenza di trascinamento per ogni colonna.

Questo codice imposta l'opacità della colonna su 40% quando l'utente inizia a trascinarla, poi la reimposta su 100% al termine dell'evento di trascinamento.

function handleDragStart(e) {
  this.style.opacity = '0.4';
}

function handleDragEnd(e) {
  this.style.opacity = '1';
}

let items = document.querySelectorAll('.container .box');
items.forEach(function (item) {
  item.addEventListener('dragstart', handleDragStart);
  item.addEventListener('dragend', handleDragEnd);
});

Il risultato è visibile nella seguente demo di Glitch. Trascina un elemento e la sua opacità cambia. Poiché l'elemento di origine ha l'evento dragstart, l'impostazione di this.style.opacity sul 40% fornisce all'utente un feedback visivo che indica che l'elemento è la selezione corrente in movimento. Quando rilasci l'elemento, l'elemento di origine riprende l'opacità del 100%, anche se non hai ancora definito il comportamento di rilascio.

Aggiungere altri indicatori visivi

Per aiutare l'utente a capire come interagire con l'interfaccia, utilizza i gestori di eventi dragenter, dragover e dragleave. In questo esempio, le colonne sono destinazioni di rilascio oltre a essere trascinabili. Aiuta l'utente a comprendere questo aspetto rendendo il bordo tratteggiate quando tiene premuto un elemento trascinato sopra una colonna. Ad esempio, nel codice CSS potresti creare una classe over per gli elementi che sono target di rilascio:

.box.over {
  border: 3px dotted #666;
}

Poi, in JavaScript, configura i gestori degli eventi, aggiungi la classe over quando la colonna viene trascinata e rimuovila quando l'elemento trascinato esce. Nell'handle dragend ci assicuriamo inoltre di rimuovere le classi alla fine del trascinamento.

document.addEventListener('DOMContentLoaded', (event) => {

  function handleDragStart(e) {
    this.style.opacity = '0.4';
  }

  function handleDragEnd(e) {
    this.style.opacity = '1';

    items.forEach(function (item) {
      item.classList.remove('over');
    });
  }

  function handleDragOver(e) {
    e.preventDefault();
    return false;
  }

  function handleDragEnter(e) {
    this.classList.add('over');
  }

  function handleDragLeave(e) {
    this.classList.remove('over');
  }

  let items = document.querySelectorAll('.container .box');
  items.forEach(function(item) {
    item.addEventListener('dragstart', handleDragStart);
    item.addEventListener('dragover', handleDragOver);
    item.addEventListener('dragenter', handleDragEnter);
    item.addEventListener('dragleave', handleDragLeave);
    item.addEventListener('dragend', handleDragEnd);
    item.addEventListener('drop', handleDrop);
  });
});

Esistono alcuni punti che vale la pena trattare in questo codice:

  • L'azione predefinita per l'evento dragover è impostare la proprietà dataTransfer.dropEffect su "none". La proprietà dropEffect viene descritta più avanti in questa pagina. Per il momento, tieni presente che impedisce l'attivazione dell'evento drop. Per eseguire l'override di questo comportamento, chiama e.preventDefault(). Un'altra buona prassi è restituirefalse nello stesso gestore.

  • Il gestore eventi dragenter viene utilizzato per attivare/disattivare la classe over anziché dragover. Se utilizzi dragover, l'evento viene attivato ripetutamente mentre l'utente tiene premuto l'elemento trascinato sopra una colonna, facendo sì che la classe CSS venga attivata e disattivata ripetutamente. Il browser esegue così un'enorme quantità di lavoro di rendering non necessario, che può influire sull'esperienza utente. Ti consigliamo vivamente di ridurre al minimo i rielaborazioni e, se devi utilizzare dragover, valuta la possibilità di limitare o eliminare il ritardo dell'ascoltatore di eventi.

Completare il lancio

Per elaborare il drop, aggiungi un gestore di eventi per l'evento drop. Nell'handler drop devi impedire il comportamento predefinito del browser per le interruzioni, che solitamente è una sorta di reindirizzamento fastidioso. Per farlo, chiama e.stopPropagation().

function handleDrop(e) {
  e.stopPropagation(); // stops the browser from redirecting.
  return false;
}

Assicurati di registrare il nuovo gestore insieme agli altri gestori:

  let items = document.querySelectorAll('.container .box');
  items.forEach(function(item) {
    item.addEventListener('dragstart', handleDragStart);
    item.addEventListener('dragover', handleDragOver);
    item.addEventListener('dragenter', handleDragEnter);
    item.addEventListener('dragleave', handleDragLeave);
    item.addEventListener('dragend', handleDragEnd);
    item.addEventListener('drop', handleDrop);
  });

Se esegui il codice a questo punto, l'elemento non viene inserito nella nuova posizione. Per farlo, utilizza l'oggetto DataTransfer.

La proprietà dataTransfer contiene i dati inviati in un'azione di trascinamento. dataTransfer viene impostato nell'evento dragstart e letto o gestito nell'evento di rilascio. La chiamata e.dataTransfer.setData(mimeType, dataPayload) consente di impostare il tipo MIME e il payload dei dati dell'oggetto.

In questo esempio, consentiamo agli utenti di riorganizzare l'ordine delle colonne. Per farlo, devi prima memorizzare il codice HTML dell'elemento di origine quando inizia il trascinamento:

function handleDragStart(e) {
  this.style.opacity = '0.4';

  dragSrcEl = this;

  e.dataTransfer.effectAllowed = 'move';
  e.dataTransfer.setData('text/html', this.innerHTML);
}

Nell'evento drop, elabori il trascinamento della colonna impostando l'HTML della colonna di origine sull'HTML della colonna di destinazione in cui hai trascinato i dati. Ciò include il controllo che l'utente non inserisca nuovamente la colonna da cui ha trascinato.

function handleDrop(e) {
  e.stopPropagation();

  if (dragSrcEl !== this) {
    dragSrcEl.innerHTML = this.innerHTML;
    this.innerHTML = e.dataTransfer.getData('text/html');
  }

  return false;
}

Puoi vedere il risultato nella seguente demo. Per farlo, devi avere un browser per computer. L'API Drag and Drop non è supportata sui dispositivi mobili. Trascina la colonna A sopra la colonna B e osserva come cambiano di posizione:

Altre proprietà di trascinamento

L'oggetto dataTransfer espone proprietà per fornire un feedback visivo all'utente durante la procedura di trascinamento e per controllare la modalità di risposta di ogni destinazione di rilascio a un determinato tipo di dati.

  • dataTransfer.effectAllowed limita il "tipo di trascinamento" che l'utente può eseguire sull'elemento. Viene utilizzato nel modello di elaborazione con trascinamento per inizializzare dropEffect durante gli eventi dragenter e dragover. La proprietà può avere i seguenti valori: none, copy, copyLink, copyMove, link, linkMove, move, all e uninitialized.
  • dataTransfer.dropEffect controlla il feedback che l'utente riceve durante gli eventi dragenter e dragover. Quando l'utente mantiene il cursore sopra un elemento di destinazione, il cursore del browser indica il tipo di operazione che verrà eseguita, ad esempio una copia o una mossa. L'effetto può assumere uno dei seguenti valori: none, copy, link, move.
  • e.dataTransfer.setDragImage(imgElement, x, y) significa che, anziché utilizzare il feedback "immagine fantasma" predefinito del browser, puoi impostare un'icona di trascinamento.

Caricamento file

Questo semplice esempio utilizza una colonna sia come origine che come destinazione del trascinamento. Questo potrebbe accadere in un'interfaccia utente che chiede all'utente di riorganizzare gli elementi. In alcuni casi, la destinazione e l'origine del trascinamento potrebbero essere tipi di elementi diversi, ad esempio in un'interfaccia in cui l'utente deve selezionare un'immagine come immagine principale di un prodotto trascinandola su un target.

Il trascinamento viene spesso utilizzato per consentire agli utenti di trascinare elementi dal desktop in un'applicazione. La differenza principale riguarda l'handler drop. Invece di utilizzare dataTransfer.getData() per accedere ai file, i relativi dati sono contenuti nella proprietà dataTransfer.files:

function handleDrop(e) {
  e.stopPropagation(); // Stops some browsers from redirecting.
  e.preventDefault();

  var files = e.dataTransfer.files;
  for (var i = 0, f; (f = files[i]); i++) {
    // Read the File objects in this FileList.
  }
}

Puoi trovare ulteriori informazioni su questo argomento in Trascinamento personalizzato.

Altre risorse