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 avectouchstart/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 objetDataTransferpour 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
- Démarrer le glissement :
- Le processus commence quand l'utilisateur clique sur l'élément et maintient le bouton de la souris enfoncé.
- 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.
- 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.
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 :
- 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). - 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éfinitactive = true. Le décalage est important — sans lui, l'icône reviendrait à l'origine à chaque nouveau glissement. - Déplacement (
mousemove) : Tant queactiveesttrue, le script calcule la distance parcourue par le curseur et l'applique comme transformationtranslate3d, de sorte que l'icône suit le pointeur. Siactiveestfalse, le gestionnaire retourne immédiatement sans rien faire. - Dépôt (
mouseup) : Quand vous relâchez le bouton, le script enregistre la position finale, définitactive = false, et appelleisInsidepour 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. - Test de collision (
isInside) : Cette fonction auxiliaire compare les rectangles de délimitation des deux éléments avecgetBoundingClientRect()et renvoietrueuniquement quand l'ampoule est entièrement contenue dans les bords de la zone sombre.
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 :
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.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.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énement | Se déclenche sur | Quand |
|---|---|---|
dragstart | l'élément glissé | au moment où le glissement commence |
dragover | la cible de dépôt | en continu tant qu'un élément glissable est au-dessus |
drop | la cible de dépôt | quand l'utilisateur relâche au-dessus |
dragend | l'é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));
});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.