API HTML5 Glisser-déposer

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

Créer du contenu déplaçable

Dans la plupart des navigateurs, le texte sélectionné, les images et les liens peuvent être déplaçables par défaut. Par exemple, si vous faites glisser un lien sur une page Web, une petite zone contenant 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 de glisser-déposer HTML5.

Pour rendre un objet déplaçable, définissez draggable=true sur cet élément. Vous pouvez faire glisser tous les éléments, y compris les images, les fichiers, les liens, les fichiers et tout autre balisage de votre page.

L'exemple suivant crée une interface pour réorganiser les colonnes qui ont été mises en page avec la grille CSS. Le balisage de base des colonnes se présente comme suit, avec l'attribut draggable défini pour chaque colonne 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 "container" et "box". Le seul CSS associé à la fonctionnalité de déplacement 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 zone.

.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 produit. 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 les événements suivants:

Pour gérer le flux de déplacement, vous avez besoin d'un type d'élément source (où commence le déplacement), de la charge utile de données (l'élément que vous faites glisser) et d'une cible (une zone pour attraper le déplacement). L'élément source peut correspondre à 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 supprimer. Tous les éléments ne peuvent pas être des cibles. Par exemple, votre cible ne peut pas être une image.

Commencer et terminer une séquence de déplacement

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

Ce code définit l'opacité de la colonne sur 40% lorsque l'utilisateur commence à la faire glisser, puis la ramène à 100% à la fin de l'événement de déplacement.

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 de Glitch suivante. Faites glisser un élément et son opacité change. Étant donné que l'élément source comporte l'événement dragstart, le fait de définir this.style.opacity sur 40% permet d'indiquer à l'utilisateur qu'il s'agit de la sélection en cours de déplacement. Lorsque vous déposez l'élément, l'élément source revient à une opacité de 100 %, même si vous n'avez pas encore défini ce comportement.

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 à le comprendre en mettant la bordure en pointillés lorsqu'il maintient un élément qu'il a glissé sur 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 vous faites glisser la colonne et supprimez-la lorsque l'élément déplacé quitte l'emplacement. Dans le gestionnaire dragend, nous assurons également de supprimer les classes à la fin du déplacement.

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

Il y a quelques points à aborder 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 l'instant, sachez simplement que cela empêche le déclenchement de l'événement drop. Pour ignorer ce comportement, appelez e.preventDefault(). Une autre bonne pratique consiste à 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 fois pendant que l'utilisateur maintient l'élément qu'il a déplacé au-dessus d'une colonne, ce qui entraîne le basculement répété de la classe CSS. De ce fait, le navigateur effectue de nombreuses tâches de rendu inutiles, ce qui peut affecter l'expérience utilisateur. Nous vous recommandons vivement de minimiser les redessins et, si vous devez utiliser dragover, envisagez de réguler ou de supprimer votre écouteur d'événements.

Terminer le lancement

Pour traiter la suppression, ajoutez un écouteur d'événements pour l'événement drop. Dans le gestionnaire drop, vous devez empêcher le comportement par défaut du navigateur pour les suppressions, 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 n'est pas déposé au nouvel emplacement. Pour ce faire, utilisez l'objet DataTransfer.

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

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 déplacement:

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 la suppression de colonne en définissant le code HTML de la colonne source sur le code HTML de la colonne cible dans laquelle vous avez déposé les données. Cela inclut la vérification que l'utilisateur ne revient pas sur la colonne à partir de laquelle il a été déplacé.

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 que cela fonctionne, vous avez besoin d'un navigateur pour ordinateur. L'API de glisser-déposer n'est pas disponible sur mobile. Faites glisser et relâchez la colonne A au-dessus de la colonne B. Notez les changements d'emplacement:

Autres propriétés de déplacement

L'objet dataTransfer expose les propriétés pour fournir un retour visuel à l'utilisateur pendant le processus de déplacement et contrôler la manière dont chaque cible de dépôt répond à un type de données particulier.

  • dataTransfer.effectAllowed limite le "type de déplacement" que l'utilisateur peut effectuer sur l'élément. Elle est utilisée 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 à effectuer, par exemple une copie ou un déplacement. L'effet peut prendre l'une des valeurs suivantes: none, copy, link, move.
  • e.dataTransfer.setDragImage(imgElement, x, y) signifie qu'au lieu d 'utiliser le retour d'image fantôme par défaut du navigateur, vous pouvez définir une icône de déplacement.

Importation de fichier

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

Le glisser-déposer est fréquemment utilisé pour permettre aux utilisateurs de faire glisser des éléments de leur ordinateur vers une application. La principale différence réside dans le 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 à ce sujet, consultez l'article Glisser-déposer personnalisé.

Autres ressources