Drag
Drop
Html5
Api
Javascript
Ux
Interactivité
Maîtrisez le Drag & Drop natif HTML5 : API Drag & Drop, événements, fichiers, DataTransfer. 13+ exemples prêts à l'emploi sans jQuery.
Introduction : Drag & Drop natif
L'API Drag & Drop HTML5 permet :
- Glisser des éléments DOM et les déposer ailleurs
- Upload de fichiers par glisser-déposer (drag-and-drop zones)
- Partager des données entre les éléments
- Réorganiser des contenus de manière intuitive
Avantage : Zéro dépendance externe. L'API est native et supportée par tous les navigateurs modernes.
Les événements Drag & Drop
Cycle complet d'une action Drag & Drop
// 1) dragstart : utilisateur commence à glisser
element.addEventListener('dragstart', (e) => {
e.dataTransfer.effectAllowed = 'copy'; // ou 'move', 'link'
e.dataTransfer.setData('text/plain', 'data');
});
// 2) dragenter : pointeur entre dans une zone
dropZone.addEventListener('dragenter', (e) => {
dropZone.classList.add('drag-over');
});
// 3) dragover : pointeur se déplace dans la zone
dropZone.addEventListener('dragover', (e) => {
e.preventDefault(); // Nécessaire pour activer drop
e.dataTransfer.dropEffect = 'copy';
});
// 4) dragleave : pointeur sort de la zone
dropZone.addEventListener('dragleave', (e) => {
dropZone.classList.remove('drag-over');
});
// 5) drop : utilisateur relâche dans la zone
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
const data = e.dataTransfer.getData('text/plain');
console.log('Données reçues :', data);
});
// 6) dragend : drag terminé (sur l'élément original)
element.addEventListener('dragend', (e) => {
console.log('Drag terminé');
});
Points clés :
preventDefault()sur dragover et drop est obligatoiredragstartetdragendse déclenchent sur l'élément glissabledragenter,dragover,dragleave,dropsur la zone de dépôt
Rendre des éléments glissables
Attribut draggable
<!-- Élément glissable -->
<div draggable="true" id="draggable" class="card p-3">
<h3>Glisse-moi !</h3>
</div>
JavaScript pour supporter le drag
const draggable = document.getElementById('draggable');
draggable.addEventListener('dragstart', (e) => {
// Passer l'ID en tant que donnée
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', draggable.innerHTML);
e.dataTransfer.setData('text/plain', draggable.id);
// Optionnel : personnaliser l'image du curseur
const dragImage = new Image();
dragImage.src = 'drag-icon.png';
e.dataTransfer.setDragImage(dragImage, 0, 0);
draggable.style.opacity = '0.5';
});
draggable.addEventListener('dragend', () => {
draggable.style.opacity = '1';
});
Créer des zones de dépôt
Zone de dépôt simple
<!-- Zone de dépôt -->
<div id="dropZone" class="drop-zone border-2 border-dashed p-4">
Dépose un élément ici
</div>
<style>
.drop-zone {
transition: background-color 0.3s ease;
}
.drop-zone.drag-over {
background-color: lightblue;
border-color: blue;
}
</style>
Gestion des événements de la zone
const dropZone = document.getElementById('dropZone');
// Éviter le comportement par défaut du navigateur
dropZone.addEventListener('dragenter', (e) => {
e.preventDefault();
dropZone.classList.add('drag-over');
});
dropZone.addEventListener('dragover', (e) => {
e.preventDefault(); // OBLIGATOIRE
e.dataTransfer.dropEffect = 'copy';
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('drag-over');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('drag-over');
const data = e.dataTransfer.getData('text/plain');
console.log('Élément reçu :', data);
// Déplacer l'élément
const element = document.getElementById(data);
dropZone.appendChild(element);
});
DataTransfer : passer des données
Différents types de données
element.addEventListener('dragstart', (e) => {
// Texte
e.dataTransfer.setData('text/plain', 'Texte simple');
// HTML
e.dataTransfer.setData('text/html', '<b>Texte en gras</b>');
// URL
e.dataTransfer.setData('text/uri-list', 'https://example.com');
// Custom JSON
e.dataTransfer.setData('application/json', JSON.stringify({
id: 123,
name: 'Mon élément'
}));
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
// Récupérer les données
const plainText = e.dataTransfer.getData('text/plain');
const html = e.dataTransfer.getData('text/html');
const url = e.dataTransfer.getData('text/uri-list');
const json = JSON.parse(e.dataTransfer.getData('application/json'));
console.log({ plainText, html, url, json });
});
effectAllowed et dropEffect
// Sur dragstart : indiquer le type d'opération
element.addEventListener('dragstart', (e) => {
e.dataTransfer.effectAllowed = 'copy'; // 'copy', 'move', 'link', 'none'
});
// Sur dragover : indiquer l'effet visuel
dropZone.addEventListener('dragover', (e) => {
e.dataTransfer.dropEffect = 'copy'; // Affiche le curseur approprié
});
Upload de fichiers avec Drag & Drop
Zone de upload
<div id="uploadZone" class="drop-zone p-5 border-2 border-dashed text-center">
<p>Glisse des fichiers ici ou <a href="#">clique pour sélectionner</a></p>
<input type="file" id="fileInput" multiple hidden/>
</div>
Traitement des fichiers
const uploadZone = document.getElementById('uploadZone');
const fileInput = document.getElementById('fileInput');
// Gestion du drop
uploadZone.addEventListener('drop', (e) => {
e.preventDefault();
const files = e.dataTransfer.files;
handleFiles(files);
});
// Gestion du clic
uploadZone.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', (e) => {
handleFiles(e.target.files);
});
// Traitement des fichiers
function handleFiles(files) {
for (let file of files) {
console.log(`Fichier : ${file.name} (${file.size} bytes)`);
// Vérifier le type
if (file.type.startsWith('image/')) {
// C'est une image
const reader = new FileReader();
reader.onload = (e) => {
const img = document.createElement('img');
img.src = e.target.result;
img.style.maxWidth = '200px';
uploadZone.appendChild(img);
};
reader.readAsDataURL(file);
} else {
console.log('Fichier non-image :', file.type);
}
}
}
// Prévenir le drop par défaut sur tout le document
document.addEventListener('dragover', (e) => e.preventDefault());
document.addEventListener('drop', (e) => e.preventDefault());
Réorganiser des listes
Liste triable
<ul id="sortableList" class="list-group">
<li draggable="true" class="list-group-item">Élément 1</li>
<li draggable="true" class="list-group-item">Élément 2</li>
<li draggable="true" class="list-group-item">Élément 3</li>
</ul>
JavaScript pour trier
const list = document.getElementById('sortableList');
let draggedElement = null;
const items = list.querySelectorAll('li');
items.forEach(item => {
item.addEventListener('dragstart', () => {
draggedElement = item;
item.style.opacity = '0.5';
});
item.addEventListener('dragend', () => {
item.style.opacity = '1';
});
item.addEventListener('dragover', (e) => {
e.preventDefault();
});
item.addEventListener('drop', (e) => {
e.preventDefault();
if (draggedElement && draggedElement !== item) {
// Insérer avant si on est dans la moitié supérieure
list.insertBefore(draggedElement, item);
}
});
});
Retours visuels et UX
Feedback visuel pendant le drag
/* Style du curseur */
[draggable="true"] {
cursor: grab;
transition: opacity 0.2s ease;
}
[draggable="true"]:active {
cursor: grabbing;
opacity: 0.7;
}
/* Zone de dépôt active */
.drop-zone {
transition: all 0.3s ease;
border: 2px solid transparent;
}
.drop-zone.drag-over {
border-color: #007bff;
background-color: rgba(0, 123, 255, 0.1);
box-shadow: 0 0 20px rgba(0, 123, 255, 0.2);
}
/* Animation de dépôt réussi */
.drop-success {
animation: dropSuccess 0.6s ease;
}
@keyframes dropSuccess {
0% {
background-color: lightgreen;
}
100% {
background-color: white;
}
}
Animation de feedback
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
// Animation de feedback
dropZone.classList.add('drop-success');
setTimeout(() => {
dropZone.classList.remove('drop-success');
}, 600);
});
Conclusion et librairies
Drag & Drop maîtrisé : Vous pouvez implémenter des interfaces intuitives et interactives avec l'API native.
Quand utiliser des librairies :
- SortableJS : listes triables avancées
- Shopify Draggable : drag & drop haute performance
- React DnD : pour React
Mais pour 90% des cas simples, l'API native suffit amplement !