W3docs

Glisser-déposer en JavaScript

Apprenez le glisser-déposer en JavaScript : approche événements souris (mousedown/mousemove/mouseup) et API HTML5 native avec draggable, dragover, drop et DataTransfer.

Dans ce tutoriel, nous allons explorer la fonctionnalité de glisser-déposer en JavaScript, une fonctionnalité puissante qui améliore l'interactivité des pages web. Il existe deux façons de la mettre en œuvre : l'approche par événements souris (mousedown/mousemove/mouseup), qui vous donne un contrôle total sur le mouvement, et l'API HTML5 native de glisser-déposer (draggable + les événements drag*), intégrée au navigateur. Nous couvrirons les deux, verrons où chacune s'applique, puis construirons un exemple pratique où une icône d'ampoule éclaire une zone sombre quand on la fait glisser dessus.

Qu'est-ce que le glisser-déposer ?

Le glisser-déposer est une interaction d'interface utilisateur qui permet aux utilisateurs de saisir un objet et de le déplacer vers un autre emplacement sur l'écran. On le retrouve partout : déplacer des fichiers dans votre système d'exploitation, réorganiser des éléments dans un jeu, télécharger des photos en les déposant sur une page, ou réordonner une liste de tâches. Ce modèle comporte toujours trois rôles :

  • Un élément glissable — l'élément que l'utilisateur saisit.
  • Une cible de dépôt (ou zone de dépôt) — la zone qui peut le recevoir.
  • Un payload — données optionnelles transmises de la source vers la cible (par exemple, l'id de l'élément déplacé).

Choisir une approche

Les deux techniques sont valides ; choisissez en fonction de vos besoins.

  • Événements souris (mousedown, mousemove, mouseup) — vous suivez manuellement le pointeur et déplacez l'élément vous-même. Utilisez cette approche quand l'élément doit suivre le curseur pixel par pixel (curseurs, zones de dessin libre, outils de dessin, physique personnalisée). C'est aussi ce que vous étendez aux écrans tactiles avec touchstart/touchmove/touchend.
  • API HTML5 native de glisser-déposer (draggable="true" + dragstart/dragover/drop) — le navigateur gère l'image « fantôme » et le geste de glissement pour vous, et vous fournit un objet DataTransfer pour transporter des données. Utilisez cette approche pour transférer quelque chose entre des zones (téléchargements de fichiers, réorganisation de listes, dépôt d'éléments dans un panier). Elle ne fonctionne pas nativement sur les appareils tactiles.

Nous allons démontrer l'approche par événements souris dans l'exemple principal, puis résumer l'API native.

Concepts fondamentaux du glisser-déposer en JavaScript

L'algorithme du glisser-déposer

  1. Démarrer le glissement :
    • Le processus commence quand l'utilisateur clique sur l'élément et maintient le bouton de la souris enfoncé.
  2. Faire glisser l'élément :
    • Au fur et à mesure que la souris se déplace, l'élément suit le chemin du curseur sur l'écran.
  3. Déposer l'élément :
    • L'élément est relâché quand l'utilisateur lâche le bouton de la souris, plaçant l'élément à une nouvelle position.

Comprendre les zones de dépôt

Les zones de dépôt sont des zones désignées pour recevoir les éléments glissables. Ces zones détectent quand un objet glissable se trouve au-dessus d'elles et peuvent déclencher des actions spécifiques en réponse.

Info

Assurez-vous que votre fonctionnalité de glisser-déposer est compatible avec le toucher. Les utilisateurs mobiles doivent pouvoir glisser et déposer avec des gestes tactiles. Envisagez d'implémenter les événements tactiles (touchstart, touchmove, touchend) ou d'utiliser une bibliothèque légère pour la compatibilité multi-appareils.

Exemple interactif : zone claire et sombre

Mettons la théorie en pratique avec un exemple simple mais interactif. Nous allons utiliser une icône d'ampoule comme objet glissable. Quand cette icône est déplacée sur une zone sombre, la zone s'illumine, simulant l'effet d'une lumière allumée.

Mise en place du HTML et du CSS

D'abord, nous définissons la structure de base et le style. Nous incluons une boîte sombre et une icône d'ampoule.

Structure HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Interactive Lighting with Drag and Drop</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" />
<style>
  #darkArea {
    width: 300px;
    height: 300px;
    background-color: #333;
    position: relative;
    margin-top: 20px;
  }
  #lightIcon {
    font-size: 48px;
    color: #ccc;
    cursor: pointer;
    position: absolute;
  }
</style>
</head>
<body>
<div id="main">
  <div id="darkArea"></div>
  <i id="lightIcon" class="fas fa-lightbulb"></i>
</div>

<script>
// JavaScript will be added here.
</script>
</body>
</html>

Implémentation du JavaScript

Maintenant, ajoutons la fonctionnalité pour rendre l'ampoule glissable et réactive à la zone sombre.

Explication du code JavaScript

<script>
  // Get references to the light bulb icon and the dark area on the webpage
  var lightIcon = document.getElementById("lightIcon");
  var darkArea = document.getElementById("darkArea");

  // Variables to track whether the dragging is active and to store position data
  var active = false;
  var initialX, initialY, currentX, currentY, xOffset = 0, yOffset = 0;

  // Listen for the mouse down event on the light bulb icon
  lightIcon.addEventListener("mousedown", function(e) {
    // Record the starting position of the mouse and adjust by any existing offset
    initialX = e.clientX - xOffset;
    initialY = e.clientY - yOffset;
    // Set the active flag to true, indicating that dragging has started
    active = true;
  });

  // Listen for mouse movement across the entire document
  document.addEventListener("mousemove", function(e) {
    // If not dragging, don't do anything
    if (!active) return;
    // Calculate the new position of the mouse
    currentX = e.clientX - initialX;
    currentY = e.clientY - initialY;
    // Update the offset with the new position
    xOffset = currentX;
    yOffset = currentY;
    // Move the light bulb icon to the new position
    lightIcon.style.transform = "translate3d(" + currentX + "px, " + currentY + "px, 0)";
  });

  // Listen for the mouse up event across the entire document
  document.addEventListener("mouseup", function() {
    // Save the final position of the light bulb
    initialX = currentX;
    initialY = currentY;
    // Set the active flag to false, indicating dragging has ended
    active = false;
    // Check if the light bulb is inside the dark area
    if (isInside(darkArea, lightIcon)) {
      // Change the background color of the dark area to yellow
      darkArea.style.backgroundColor = "yellow";
      // Change the color of the light bulb to yellow
      lightIcon.style.color = "yellow";
    } else {
      // Revert the dark area's color to dark
      darkArea.style.backgroundColor = "#333";
      // Revert the light bulb's color to gray
      lightIcon.style.color = "#ccc";
    }
  });

  // Function to check if the light bulb is inside the dark area
  function isInside(container, element) {
    // Get the position of the container and the element
    var containerRect = container.getBoundingClientRect();
    var elementRect = element.getBoundingClientRect();
    // Return true if the element is within the container's boundaries
    return (
      elementRect.left >= containerRect.left &&
      elementRect.right <= containerRect.right &&
      elementRect.top >= containerRect.top &&
      elementRect.bottom <= containerRect.bottom
    );
  }
</script>

Ce script rend l'ampoule glissable avec la souris et réagit quand vous la déposez sur la zone sombre. Voici ce que fait chaque partie :

  1. Configuration : Le script récupère des références à l'icône de l'ampoule et à la zone sombre, et déclare les variables utilisées pour suivre le glissement (active, la position initiale de la souris, le décalage courant).
  2. Début du glissement (mousedown) : Quand vous appuyez sur le bouton de la souris sur l'ampoule, le script enregistre la position initiale du curseur (moins tout décalage d'un glissement précédent) et définit active = true. Le décalage est important — sans lui, l'icône reviendrait à l'origine à chaque nouveau glissement.
  3. Déplacement (mousemove) : Tant que active est true, le script calcule la distance parcourue par le curseur et l'applique comme transformation translate3d, de sorte que l'icône suit le pointeur. Si active est false, le gestionnaire retourne immédiatement sans rien faire.
  4. Dépôt (mouseup) : Quand vous relâchez le bouton, le script enregistre la position finale, définit active = false, et appelle isInside pour déterminer si l'ampoule a atterri dans la zone sombre. Si c'est le cas, la zone et l'ampoule deviennent jaunes ; sinon, elles reprennent leurs couleurs par défaut.
  5. Test de collision (isInside) : Cette fonction auxiliaire compare les rectangles de délimitation des deux éléments avec getBoundingClientRect() et renvoie true uniquement quand l'ampoule est entièrement contenue dans les bords de la zone sombre.
Info

Utilisez translate3d dans vos transformations CSS pour les éléments glissables. Cela exploite l'accélération GPU, ce qui produit des mouvements plus fluides et réduit la charge CPU — crucial pour les applications gourmandes en performances.

Exemple complet

Il est maintenant temps de voir tout cela en action :

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Interactive Lighting with Drag and Drop</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" />
    <style>
      #darkArea {
        width: 300px;
        height: 300px;
        background-color: #333;
        position: relative;
        margin-top: 20px;
      }
      #lightIcon {
        font-size: 48px;
        color: #ccc;
        cursor: pointer;
        position: absolute;
      }
    </style>
  </head>
  <body>
    <div id="main">
     <p>Move the light into the dark area to light it up!</p>
      <div id="darkArea"></div>
      <i id="lightIcon" class="fas fa-lightbulb"></i>
    </div>

    <script>
      // Get references to the light bulb icon and the dark area on the webpage
      var lightIcon = document.getElementById("lightIcon");
      var darkArea = document.getElementById("darkArea");

      // Variables to track whether the dragging is active and to store position data
      var active = false;
      var initialX,
        initialY,
        currentX,
        currentY,
        xOffset = 0,
        yOffset = 0;

      // Listen for the mouse down event on the light bulb icon
      lightIcon.addEventListener("mousedown", function (e) {
        // Record the starting position of the mouse and adjust by any existing offset
        initialX = e.clientX - xOffset;
        initialY = e.clientY - yOffset;
        // Set the active flag to true, indicating that dragging has started
        active = true;
      });

      // Listen for mouse movement across the entire document
      document.addEventListener("mousemove", function (e) {
        // If not dragging, don't do anything
        if (!active) return;
        // Calculate the new position of the mouse
        currentX = e.clientX - initialX;
        currentY = e.clientY - initialY;
        // Update the offset with the new position
        xOffset = currentX;
        yOffset = currentY;
        // Move the light bulb icon to the new position
        lightIcon.style.transform = "translate3d(" + currentX + "px, " + currentY + "px, 0)";
      });

      // Listen for the mouse up event across the entire document
      document.addEventListener("mouseup", function () {
        // Save the final position of the light bulb
        initialX = currentX;
        initialY = currentY;
        // Set the active flag to false, indicating dragging has ended
        active = false;
        // Check if the light bulb is inside the dark area
        if (isInside(darkArea, lightIcon)) {
          // Change the background color of the dark area to yellow
          darkArea.style.backgroundColor = "yellow";
          lightIcon.style.color = "yellow";
        } else {
          // Revert the dark area's color to dark
          darkArea.style.backgroundColor = "#333";
          // Revert the light bulb's color to gray
          lightIcon.style.color = "#ccc";
        }
      });

      // Function to check if the light bulb is inside the dark area
      function isInside(container, element) {
        // Get the position of the container and the element
        var containerRect = container.getBoundingClientRect();
        var elementRect = element.getBoundingClientRect();
        // Return true if the element is within the container's boundaries
        return elementRect.left >= containerRect.left && elementRect.right <= containerRect.right && elementRect.top >= containerRect.top && elementRect.bottom <= containerRect.bottom;
      }
    </script>
  </body>
</html>

Principaux événements souris utilisés :

  1. mousedown : Cet événement est déclenché quand l'utilisateur appuie sur le bouton de la souris sur l'icône de l'ampoule. Il marque le début du glissement et enregistre la position initiale du curseur.
  2. mousemove : Cet événement se déclenche quand la souris se déplace. Si le glissement est actif (c'est-à-dire que le bouton de la souris est toujours enfoncé), il calcule la nouvelle position de l'icône en fonction du mouvement du curseur et met à jour la position de l'ampoule sur l'écran.
  3. mouseup : Cet événement se produit quand l'utilisateur relâche le bouton de la souris, marquant la fin du glissement. Il vérifie si l'ampoule se trouve dans les limites de la zone sombre pour décider de changer ou non la couleur d'arrière-plan de la zone.

Comme nous l'avons appris dans l'article sur les Événements souris, ces événements sont fondamentaux pour créer une fonctionnalité de glisser-déposer interactive, permettant aux éléments d'une page web d'être déplacés et interagis dynamiquement avec une souris. (Si vous souhaitez positionner l'élément avec précision, le chapitre Coordonnées JavaScript explique la différence entre clientX/clientY, pageX/pageY, et getBoundingClientRect().)

L'API HTML5 native de glisser-déposer

La technique par événements souris est idéale quand vous avez besoin qu'un élément suive exactement le curseur. Mais quand votre véritable objectif est de transférer quelque chose — déposer une carte dans une colonne, un article dans un panier, un fichier dans une zone de téléchargement — l'API de glisser-déposer intégrée au navigateur nécessite moins de code et gère le geste de glissement pour vous.

Rendre un élément glissable

N'importe quel élément devient glissable en définissant l'attribut draggable à true. Les liens et les images sont glissables par défaut ; tout le reste ne l'est pas.

<div id="item" draggable="true">Drag me</div>
<div id="dropzone">Drop here</div>

Les événements de glisser-déposer

L'API déclenche une séquence d'événements. Les plus importants sont :

ÉvénementSe déclenche surQuand
dragstartl'élément glisséau moment où le glissement commence
dragoverla cible de dépôten continu tant qu'un élément glissable est au-dessus
dropla cible de dépôtquand l'utilisateur relâche au-dessus
dragendl'élément glisséquand le glissement se termine (déposé ou annulé)

Transporter des données avec DataTransfer

Chaque événement de glissement expose event.dataTransfer, un objet utilisé pour attacher un payload dans dragstart et le récupérer dans drop.

const item = document.getElementById("item");
const zone = document.getElementById("dropzone");

// 1. Attach a payload when the drag starts.
item.addEventListener("dragstart", (e) => {
  e.dataTransfer.setData("text/plain", item.id);
});

// 2. By default elements are NOT valid drop targets.
//    Prevent the default to allow a drop.
zone.addEventListener("dragover", (e) => {
  e.preventDefault();
});

// 3. Read the payload and move the element on drop.
zone.addEventListener("drop", (e) => {
  e.preventDefault();
  const id = e.dataTransfer.getData("text/plain");
  zone.appendChild(document.getElementById(id));
});
Avertissement

L'erreur la plus courante avec l'API native est d'oublier e.preventDefault() à l'intérieur du gestionnaire dragover. Sans cela, le navigateur rejette l'élément comme cible de dépôt et l'événement drop ne se déclenche jamais. Les données définies avec setData ne sont disponibles qu'une fois que drop s'exécute — elles ne peuvent pas être lues pendant dragover pour des raisons de sécurité.

La paire setData(format, value) / getData(format) vous permet de transporter du texte brut, des URL (text/uri-list), du HTML, ou vos propres clés de chaîne personnalisées. Pour les téléchargements de fichiers, lisez e.dataTransfer.files, qui est une FileList comme un <input type="file">.

Événements souris vs. API native en un coup d'œil

  • Choisissez les événements souris quand le mouvement doit être libre et précis au pixel près, ou quand vous avez besoin du support tactile.
  • Choisissez l'API native quand vous déplacez un élément discret d'un endroit à un autre et souhaitez que le navigateur gère l'image et le geste de glissement.

Pour approfondir le système d'événements sous-jacent, consultez Introduction aux événements navigateur, et pour les interactions accessibles, n'oubliez pas que le glisser-déposer doit toujours avoir une alternative au clavier — voir Considérations d'accessibilité du DOM.

Conclusion

Le glisser-déposer rend les interfaces directes et intuitives. Vous disposez maintenant de deux outils pour cela : l'approche par événements souris, qui déplace un élément pixel par pixel sous contrôle manuel total, et l'API HTML5 native de glisser-déposer, qui laisse le navigateur gérer le geste pendant que vous transportez des données via DataTransfer. Choisissez les événements souris pour un mouvement libre et le support tactile, et l'API native pour transférer des éléments entre des zones. Quelle que soit votre choix, fournissez une alternative accessible au clavier afin que chaque utilisateur puisse accomplir la même tâche.

Pratique

Pratique
Quelles affirmations sont vraies concernant la fonctionnalité de glisser-déposer en JavaScript ?
Quelles affirmations sont vraies concernant la fonctionnalité de glisser-déposer en JavaScript ?
Was this page helpful?