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 qualsiasi elemento può essere trascinato, incluse immagini, file, link, file o qualsiasi markup nella pagina.

L'esempio seguente crea un'interfaccia per riorganizzare le colonne che sono state disposte con la griglia CSS. 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>

Questo è il CSS per gli elementi container e box. 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 il 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&#39;elemento trascinato) e un target (un&#39;area in cui avviene il rilascio). L&#39;elemento source può essere di quasi qualsiasi tipo. Il target è la zona di rilascio o l&#39;insieme di zone di rilascio che accettano i dati che l&#39;utente sta tentando di inserire. Non tutti gli elementi possono essere target. Ad esempio, il target non può essere un'immagine.

Iniziare 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 al 40% quando l'utente inizia a trascinarla, quindi la riporta al 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. Aiutalo 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, nel codice JavaScript, configura 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);
  });
});

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 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 è restituirefalse nello stesso gestore.

  • Il gestore di 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. Consigliamo vivamente di ridurre al minimo i ritracciamenti e, se devi utilizzare dragover, valuta la limitazione o il debouncing del listener di eventi.

Completare il lancio

Per elaborare il rilascio, aggiungi un listener di eventi per l'evento drop. Nell'handler drop devi impedire il comportamento predefinito del browser per i abbandoni, 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 rilasciato 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 viene 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 innanzitutto archiviare 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 della colonna impostando l'HTML della colonna di origine sul codice HTML della colonna di destinazione per cui hai rilasciato 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 di trascinamento non è supportata sui dispositivi mobili. Trascina e rilascia la colonna A sopra la colonna B e osserva come cambiano 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 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 tiene il puntatore sopra un elemento di destinazione, 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 "immagine fantasma" 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 avvenire in una UI che chiede all'utente di riorganizzare gli elementi. In alcune situazioni, il target di trascinamento e l'origine potrebbero essere tipi di elementi diversi, ad esempio in un'interfaccia in cui l'utente deve selezionare un'immagine come principale di un prodotto trascinando l'immagine selezionata su un target.

La funzionalità di trascinamento viene spesso utilizzata per consentire agli utenti di trascinare elementi dal desktop a un'applicazione. La differenza principale è nel tuo gestore drop. Anziché utilizzare dataTransfer.getData() per accedere ai file, i relativi 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.
  }
}

Per ulteriori informazioni, consulta la sezione Trascinamento personalizzato.

Altre risorse