API HTML5 Glisser-déposer

Cet article explique les principes de base du glisser-déposer.

Créer du contenu déplaçable

Dans la plupart des navigateurs, les sélections de texte, les images et les liens sont déplaçables par défaut. Par exemple, si vous faites glisser un lien sur une page Web, une petite zone avec un titre et une URL s'affiche. Vous pouvez la déposer dans la barre d'adresse ou sur le bureau pour créer un raccourci ou accéder au lien. Pour rendre d'autres types de contenus déplaçables, vous devez utiliser les API HTML5 de glisser-déposer.

Pour rendre un objet déplaçable, définissez draggable=true sur cet élément. Presque tout peut être activé pour le glisser-déposer, y compris les images, les fichiers, les liens, les fichiers ou toute balise de votre page.

L'exemple suivant crée une interface permettant de réorganiser les colonnes qui ont été mises en page avec CSS Grid. Le balisage de base des colonnes se présente comme suit, avec l'attribut draggable de chaque colonne défini sur 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>

Voici le code CSS des éléments de conteneur et de boîte. La seule propriété CSS liée à la fonctionnalité de glisser-déposer est la propriété cursor: move. Le reste du code contrôle la mise en page et le style des éléments de conteneur et de boîte.

.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;
}

À ce stade, vous pouvez faire glisser les éléments, mais rien d'autre ne se passe. Pour ajouter un comportement, vous devez utiliser l'API JavaScript.

Écouter les événements de déplacement

Pour surveiller le processus de déplacement, vous pouvez écouter l'un des événements suivants:

Pour gérer le flux de glisser-déposer, vous avez besoin d'un élément source (à partir duquel le glissement commence), de la charge utile de données (l'élément glissé) et d'une cible (une zone pour récupérer le dépôt). L'élément source peut être presque n'importe quel type d'élément. La cible est la zone de dépôt ou l'ensemble de zones de dépôt qui accepte les données que l'utilisateur tente de déposer. Tous les éléments ne peuvent pas être des cibles. Par exemple, votre cible ne peut pas être une image.

Démarrer et terminer une séquence de glisser-déposer

Après avoir défini des attributs draggable="true" sur votre contenu, associez un gestionnaire d'événements dragstart pour démarrer la séquence de glisser-déposer pour chaque colonne.

Ce code définit l'opacité de la colonne sur 40% lorsque l'utilisateur commence à la faire glisser, puis la rétablit à 100% à la fin de l'événement de glissement.

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);
});

Vous pouvez voir le résultat dans la démonstration Glitch suivante. Faites glisser un élément, et son opacité change. Étant donné que l'élément source contient l'événement dragstart, définir this.style.opacity sur 40% fournit à l'utilisateur un indice visuel indiquant que cet élément est la sélection en cours de déplacement. Lorsque vous déposez l'élément, l'élément source retrouve une opacité de 100 %, même si vous n'avez pas encore défini le comportement de dépôt.

Ajouter des repères visuels supplémentaires

Pour aider l'utilisateur à comprendre comment interagir avec votre interface, utilisez les gestionnaires d'événements dragenter, dragover et dragleave. Dans cet exemple, les colonnes sont des cibles de dépôt en plus d'être déplaçables. Aidez l'utilisateur à comprendre cela en rendant la bordure en pointillés lorsqu'il maintient un élément déplacé au-dessus d'une colonne. Par exemple, dans votre CSS, vous pouvez créer une classe over pour les éléments qui sont des cibles de dépôt:

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

Ensuite, dans votre code JavaScript, configurez les gestionnaires d'événements, ajoutez la classe over lorsque la colonne est déplacée et supprimez-la lorsque l'élément déplacé quitte la zone. Dans le gestionnaire dragend, nous nous assurons également de supprimer les classes à la fin du glisser-déposer.

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);
  });
});

Voici quelques points à prendre en compte dans ce code:

  • L'action par défaut pour l'événement dragover consiste à définir la propriété dataTransfer.dropEffect sur "none". La propriété dropEffect est abordée plus loin sur cette page. Pour le moment, sachez simplement qu'il empêche le déclenchement de l'événement drop. Pour ignorer ce comportement, appelez e.preventDefault(). Il est également recommandé de renvoyer false dans ce même gestionnaire.

  • Le gestionnaire d'événements dragenter permet d'activer/de désactiver la classe over au lieu de dragover. Si vous utilisez dragover, l'événement se déclenche à plusieurs reprises lorsque l'utilisateur maintient l'élément glissé au-dessus d'une colonne, ce qui entraîne l'activation et la désactivation répétées de la classe CSS. Cela oblige le navigateur à effectuer de nombreuses tâches de rendu inutiles, ce qui peut affecter l'expérience utilisateur. Nous vous recommandons vivement de réduire au maximum les redessins. Si vous devez utiliser dragover, envisagez de limiter ou de désactiver le délai avant réactivation de votre écouteur d'événements.

Effectuer le dépôt

Pour traiter la suppression, ajoutez un écouteur d'événement pour l'événement drop. Dans le gestionnaire drop, vous devez empêcher le comportement par défaut du navigateur pour les abandons, qui est généralement une sorte de redirection ennuyeuse. Pour ce faire, appelez e.stopPropagation().

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

Veillez à enregistrer le nouveau gestionnaire avec les autres gestionnaires:

  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);
  });

Si vous exécutez le code à ce stade, l'élément ne s'affiche pas à son nouvel emplacement. Pour ce faire, utilisez l'objet DataTransfer.

La propriété dataTransfer contient les données envoyées lors d'une action de glisser-déposer. dataTransfer est défini dans l'événement dragstart et lu ou géré dans l'événement de dépôt. Appeler e.dataTransfer.setData(mimeType, dataPayload) vous permet de définir le type MIME et la charge utile de données de l'objet.

Dans cet exemple, nous allons permettre aux utilisateurs de réorganiser l'ordre des colonnes. Pour ce faire, vous devez d'abord stocker le code HTML de l'élément source au début du glisser-déposer:

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

  dragSrcEl = this;

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

Dans l'événement drop, vous traitez le dépôt de colonne en définissant le code HTML de la colonne source sur celui de la colonne cible sur laquelle vous avez déposé les données. Cela inclut de vérifier que l'utilisateur ne dépose pas l'élément sur la même colonne à partir de laquelle il l'a fait glisser.

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

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

  return false;
}

Vous pouvez voir le résultat dans la démonstration suivante. Pour ce faire, vous avez besoin d'un navigateur pour ordinateur. L'API Drag and Drop n'est pas compatible avec les appareils mobiles. Faites glisser la colonne A au-dessus de la colonne B, puis relâchez-la. Vous remarquerez qu'elles changent de place:

Autres propriétés de glisser-déposer

L'objet dataTransfer expose des propriétés pour fournir un retour visuel à l'utilisateur pendant le processus de glisser-déposer et contrôler la façon dont chaque cible de dépôt répond à un type de données particulier.

  • dataTransfer.effectAllowed restreint le type de "glissement" que l'utilisateur peut effectuer sur l'élément. Il est utilisé dans le modèle de traitement par glisser-déposer pour initialiser dropEffect lors des événements dragenter et dragover. La propriété peut avoir les valeurs suivantes: none, copy, copyLink, copyMove, link, linkMove, move, all et uninitialized.
  • dataTransfer.dropEffect contrôle les commentaires que l'utilisateur reçoit lors des événements dragenter et dragover. Lorsque l'utilisateur maintient son pointeur sur un élément cible, le curseur du navigateur indique le type d'opération qui va avoir lieu, par exemple une copie ou un déplacement. L'effet peut prendre l'une des valeurs suivantes: none, copy, link ou move.
  • e.dataTransfer.setDragImage(imgElement, x, y) signifie que, au lieu d'utiliser le retour d'image fantôme par défaut du navigateur, vous pouvez définir une icône de glisser-déposer.

Importer des fichiers

Cet exemple simple utilise une colonne à la fois comme source et comme cible de glisser-déposer. Cela peut se produire dans une UI qui demande à l'utilisateur de réorganiser des éléments. Dans certains cas, la cible de glisser-déposer et la source peuvent être de différents types d'éléments, comme dans une interface où l'utilisateur doit sélectionner une image comme image principale d'un produit en la faisant glisser sur une cible.

La fonctionnalité de glisser-déposer est souvent utilisée pour permettre aux utilisateurs de faire glisser des éléments de leur bureau vers une application. La principale différence réside dans votre gestionnaire drop. Au lieu d'utiliser dataTransfer.getData() pour accéder aux fichiers, leurs données sont contenues dans la propriété 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.
  }
}

Pour en savoir plus, consultez la section Glisser-déposer personnalisé.

Autres ressources