API HTML5 Drag and Drop

Questo post spiega le nozioni di base del trascinamento.

Creare contenuti trascinabili

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 sulla barra degli indirizzi o sul desktop per creare una scorciatoia o accedere al link. Per poter trascinare altri tipi di contenuti, devi usare le API di trascinamento di HTML5.

Per rendere un oggetto trascinabile, imposta draggable=true su quell'elemento. Il trascinamento può essere eseguito praticamente su ogni elemento, ad esempio immagini, file, link, file o qualsiasi markup sulla pagina.

L'esempio seguente crea un'interfaccia per riorganizzare le colonne disposte con la griglia CSS. Il markup di base per le colonne ha il seguente 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>

Questo è 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 contenitore e riquadro.

.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 nient'altro. Per aggiungere un comportamento, devi utilizzare l'API JavaScript.

Ascolta il trascinamento degli eventi

Per monitorare la procedura di trascinamento, puoi rimanere in ascolto dei seguenti eventi:

Per gestire il flusso di trascinamento, ti servono un tipo di elemento di origine (il punto in cui inizia il trascinamento), il payload dei dati (l'elemento che viene trascinato) e una destinazione (un'area in cui eseguire il trascinamento). L'elemento di origine può essere praticamente qualsiasi tipo di elemento. Il target è la zona di rilascio o l'insieme di zone di rilascio che accetta i dati che l'utente sta cercando di eliminare. Non tutti gli elementi possono essere target. Ad esempio, il tuo target non può essere un'immagine.

Inizio e fine di una sequenza di trascinamento

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

Questo codice imposta l'opacità della colonna al 40% quando l'utente inizia a trascinarla, poi riportala al 100% quando termina l'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 può essere visto nella seguente demo di Glitch. Trascina un elemento e la sua opacità cambia. Poiché l'elemento di origine contiene l'evento dragstart, impostare this.style.opacity su 40% fornisce un feedback visivo all'utente per capire che questo elemento è la selezione corrente che viene spostata. Quando elimini l'elemento, l'elemento di origine restituisce un'opacità del 100%, anche se non hai ancora definito il comportamento di rilascio.

Aggiungi altri indizi visivi

Per aiutare l'utente a capire come interagire con la tua interfaccia, utilizza i gestori di eventi dragenter, dragover e dragleave. In questo esempio, le colonne sono target degli elementi selezionati e possono essere trascinate. Aiuta l'utente a capirlo tratteggiando il bordo quando tiene un elemento trascinato sopra una colonna. Ad esempio, nel tuo CSS, potresti creare una classe over per gli elementi che sono destinazioni target:

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

Quindi, nel codice JavaScript, imposta i gestori di eventi, aggiungi la classe over quando la colonna viene trascinata e rimuovila quando esce l'elemento trascinato. Nel gestore 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);
  });
});

Ci sono un paio di 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 verrà trattata 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 è restituire false nello stesso gestore.

  • Il gestore di eventi dragenter viene utilizzato per attivare/disattivare la classe over anziché dragover. Se utilizzi dragover, l'evento si attiva ripetutamente mentre l'utente mantenendo l'elemento trascinato sopra una colonna, causando l'attivazione ripetuta della classe CSS. In questo modo il browser esegue molte operazioni di rendering non necessarie, il che può influire sull'esperienza utente. Ti consigliamo vivamente di ridurre al minimo i ridisegnati e, se devi utilizzare dragover, valuta la possibilità di limitare o eliminare il listener di eventi.

Completa il lancio

Per elaborare il rilascio, aggiungi un listener di eventi per l'evento drop. Nel gestore drop devi impedire il comportamento predefinito del browser per i rilasci, che in genere sono una sorta di fastidioso reindirizzamento. Per farlo, chiama il numero 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 in questo punto, l'elemento non viene spostato nella nuova posizione. A tal fine, 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 a e.dataTransfer.setData(mimeType, dataPayload) consente di impostare il tipo MIME e il payload dei dati dell'oggetto.

In questo esempio, consentiremo agli utenti di riorganizzare l'ordine delle colonne. A tale scopo, devi prima memorizzare il codice HTML dell'elemento di origine all'avvio del 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 la riduzione di colonna impostando l'HTML della colonna di origine sull'HTML della colonna di destinazione in cui hai rilasciato i dati. Questo include anche controllare che l'utente non torni nella stessa colonna da cui è stato 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 demo seguente. Affinché questa operazione funzioni, ti servirà un browser desktop. L'API Drag and Drop non è supportata sui dispositivi mobili. Trascina e rilascia la colonna A sopra la colonna B e osserva come cambia la posizione:

Altre proprietà di trascinamento

L'oggetto dataTransfer espone le proprietà per fornire un feedback visivo all'utente durante il processo di trascinamento e per controllare il modo in cui ogni destinazione di rilascio risponde 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 mediante 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 puntatore su un elemento target, il cursore del browser indica il tipo di operazione che verrà eseguita, ad esempio una copia o uno spostamento. L'effetto può assumere uno dei seguenti valori: none, copy, link, move.
  • e.dataTransfer.setDragImage(imgElement, x, y) significa che, invece di utilizzare il feedback predefinito del browser relativo all'immagine fantasma, puoi impostare un'icona di trascinamento.

Caricamento file

In questo semplice esempio viene utilizzata una colonna sia come origine del trascinamento sia come destinazione. Ciò può accadere in una UI 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 trascinando l'immagine selezionata su una destinazione.

Il trascinamento viene spesso utilizzato per consentire agli utenti di trascinare elementi dal desktop in un'applicazione. La differenza principale è nel tuo gestore drop. Anziché utilizzare dataTransfer.getData() per accedere ai file, i suoi dati vengono 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 in merito nella sezione Trascinamento personalizzato.

Altre risorse