Boire ou coder ... Pourquoi choisir?
Publié le 17 mai 2010 20:00

Drag and Drop HTML 5

Après les WebSockets, dans cet article, nous allons voir une autre nouveauté apportée par HTML 5 : le déplacement d'éléments dans la page et le suivi de ceux-ci en javacript.

Une demonstration de ce que nous allons faire ici est disponible en ligne.

Pourquoi HTML5 ?

En effet ! On pourrait très bien utiliser divers frameworks tels que jQuery permettent assez simplement d'implémenter du drag & drop d'éléments dans une page. Plusieurs problèmes à cela cependant :

  • Vous devenez dépendant du framework jQuery
  • L'ergonomie de la chose est assez discutable dès que l'on a des objets un peu complexes (dans mon cas, des tableaux imbriqués, chaque élément pouvant être déplacé dans le tableau de base ou l'un des tableaux fils (ces tableaux fils compris)).
  • La lourdeur de la chose
  • L'accessibilité de la chose pour les personnes n'ayant pas de souris est également très discutable

C'est pourquoi nous allons chercher à implémenter une fonctionnalité de drag & drop simple en HTML5. Vous pouvez déjà tester ce drag & drop de manière fonctionnelle. Et nous allons voir comment le créer !

Le tag HTML

Nous allons créer dans notre page deux éléments simples qui seront les conteneurs.

<div class="box"></div>
<div class="box></div>

Puis dans le premier conteneur nous allons ajouter un élément qui pourra être déplacé vers le second.

<div class="box"><div class="element" id="first">Hey !</div></div>

Afin de visualiser un peu mieux ceux-ci, nous pouvons ajouter un peu de style à cela :

<style type="text/css">
    .box {
        border: 1px solid #CCC;
        width: 150px;
        height: 150px;
        float: left;
        margin: 10px;
    }
    .element {
        width: 50px;
        height: 50px;
        background-color: #CCCCCC;
        margin: 5px;
        padding: 3px
    }
</style>

Nous avons maintenant tous les éléments nécessaires pour faire du drag & drop simple dans notre page. Créons un petit peu de dynamisme dans la chose afin de pouvoir les déplacer.

Les évènements

Nous avons accès à plusieurs nouveaux évènements nous permettant de mettre en place notre drag & drop.

Déplacement de l'élément

Un seul d'entre eux sera utile pour le moment cependant : ondragstart. Cet évènement est déclenché dès que vous débutez le drag & drop sur un objet qui le permet.

Pour rendre un élément draggable, nous devons lui ajouter l'attribut draggable="true". Puis dans la boite qui le contient, nous ajoutons l'évènement approprié.

<script type="text/javascript">
    function dragStart(event) {
        event.dataTransfer.effectAllowed = 'move';
        event.dataTransfer.setData("Text", event.target.getAttribute('id'));
    }
</script>

<div class="box" ondragstart="dragStart(event);">
    <div id="first" class="element" draggable="true">Hey !</div>
</div>

Analysons un peu ce code.

L'objet dataTransfer

Chacune des deux méthodes utilisées fait partie de dataTransfer. dataTransfer est un nouvel objet de HTML5. Il permet de transférer des données entre les évènements. Ainsi, lorsque dans un évènement, nous modifions une donnée dans dataTransfer, celle-ci sera accessible à tous les autres évènements suivants. Nous pouvons donc sauvegarder des données et les récupérer dans un autre évènement. Ce qui va s'avérer particulièrement pratique pour déplacer le bon objet.

effectAllowed nous permet de définir le type de déplacement que nous autorisons pour ce drag and drop. Les divers déplacements autorisés sont les suivants :

  • all - L'élément peut être copié, déplacé et lié.
  • copy - L'élément peut être copié.
  • copyLink - L'élément peut être copié et lié.
  • copyMove - L'élément peut être copié et déplacé.
  • link - L'élément peut être lié.
  • linkMove - L'élément peut être déplacé et lié.
  • move - L'élément peut être déplacé.
  • none - L'élément ne peut pas être draggé.
  • uninitialized - Valeur par défaut. Le comportement est "move" pour les éléments éditables, "link" pour les ancres et "copy" pour les autres.

setData quant à lui nous permet de définir une valeur à notre dataTransfer. Ici, nous définissons l'identifiant de l'élément. Mais vous pourriez définir ce que vous désirez et qui vous permettra de retrouver cet élément par la suite.

La clé "Text" n'est pas une clé que vous pouvez définir comme vous le désirez. Cela correspond au format de la chaine que nous transmettons. Ici, du texte.
Certains navigateurs, comme Firefox acceptent n'importe quelle valeur. D'autres, comme Chrome en revanche, exigeront que vous utilisiez la bonne.

Notre objet est maintenant déplaçable dans la page. Il nous faut maintenant le dropper dans la seconde boite.

Droppage de l'élément

Se contenter de déplacer l'élément ne va pas nous emmener très loin. Afin de pouvoir dropper l'élément, nous allons ajouter deux évènements : ondragover et ondrop.

ondragover est est exécuté lorsque l'on passe au dessus de l'élément. Il va nous permettre ici d'autoriser ou non le droppage de l'élément dans la seconde boite. Par défaut, javascript n'accepte pas que l'on droppe l'élément. En conséquent on doit retourner false afin d'arrêter la propagation de l'évènement et de ne pas avoir le comportement par défaut (et donc de pouvoir dropper l'élément).

ondrop est exécuté lorsque l'on lache l'élément dans la seconde boite. C'est à ce moment la que nous déplaçons effectivement l'élément dans le dom.

Voici donc à quoi ressemblent nos deux boites maintenant.

<script type="text/javascript">
    function dragStart(event) {
        event.dataTransfer.effectAllowed = 'move';
        event.dataTransfer.setData("Text", event.target.getAttribute('id'));
    }

    function dragOver(event) {
        return false;
    }

    function drop(event) {
        var element = event.dataTransfer.getData("Text");
        event.target.appendChild(document.getElementById(element));
        event.stopPropagation();

        return false;
    }
</script>

<div class="box" ondragstart="dragStart(event);" ondragover="return dragOver(event);" ondrop="return drop(event);">
    <div id="first" class="element" draggable="true">Hey !</div>
</div>
<div class="box" ondragstart="dragStart(event);" ondragover="return dragOver(event);" ondrop="return drop(event);">

Essayez par vous même. Vous pouvez maintenant drag/dropper l'élément d'une boite à l'autre ! :)

Vous pouvez tester directement sur la demonstration en ligne de ce drag and drop.

Tous les évènements disponibles

Nous avons vu ici comment faire quelque chose de très simple. Cependant il vous est possible de complexifier la chose en fonction de vos besoins. Plusieurs évènements sont pour cela à votre disposition.

  • dragstart - Evènement représentant le début du déplacement. Est exécuté dès que vous commencez à bouger un élément.
  • drag - Evènement exécuté à intervales réguliers lors du déplacement (le référentiel HTML5 indique toutes les 350ms). Si il retourne faux, le déplacement sera arrêté et annulé.
  • dragenter - Evènement exécuté lorsque l'élément que vous déplacez arrive dans un autre élément.
  • dragleave - Evènement exécuté lorsque l'élément que vous déplacez sors d'un autre élément.
  • dragover - Evènement exécuté lorsque vous passez au dessus d'un élément. Si vous laissez cet évènement continuer sa propagation (en ne retournant pas "faux"), il ne sera pas possible de dropper l'objet que vous déplacez dans cet élément.
  • drop - Evènement exécuté lorsque vous dropper l'objet que vous êtes en train de déplacer dans un autre élément.
  • dragend - Evènement représentant la fin du déplacement. Exécuté lorsque celui-ci s'achève.

Conclusion

A l'heure actuelle, seul Opéra ne supporte pas le drag and drop. Toutes les versions récentes des navigateurs le supportent. Cette solution est bien évidemment à préférer à toute solution intégralement javascript qui ne fera que simuler ce qui est ici geré nativement par le navigateur.

Commentaires

Palleas
Palleas dit: 17 mai 2010 22:00

Ca reste effectivement très simple, et ressemble très fortement au DragManager de Flex, j'aime!

Par contre ton exemple fonctionne chez moi avec Firefox mais pas avec Chrome ni Safari...

Johan BLEUZEN
Johan BLEUZEN dit: 17 mai 2010 22:00 Site web

Bonjour, La démo ne semble pas fonctionner sous Chrome dev en v6. J'ai regardé rapidement le source et je n'ai pas vue d'erreur notoire... Bon article, néanmoins :p

Damien
Damien dit: 17 mai 2010 22:00 Site web

C'est corrigé sous Chrome (et Safari aussi normalement). Voir l'encart rouge dans dataTransfer pour les explications.

jpvincent
jpvincent dit: 17 mai 2010 22:00 Site web

"Toutes les versions récentes des navigateurs le supportent" c'est enfoncer une porte ouverte que de dire que IE ne le supporte pas, y compris la preview d'IE9 ? :)

d'après l'expérience que tu as, est ce qu'il te semblerait facile de construire une librairie JS qui assure la rétro-compatibilité ? Les actuelles se basent sur les event onmousedown, mouseover et mouseup tout en déplaçant des objets DOM en position:absolute à l'endroit où se trouve la souris. Ca te parait compatible avec ce que les browsers ont fait de la spec ?

Damien
Damien dit: 17 mai 2010 22:00 Site web

Je t'avoue ne pas avoir testé sous IE. Je n'ai aucun windows sous la main et pas la possibilité de le faire donc. Je me suis basé sur les dires de html5demos : http://html5demos.com/

Construire ce genre de librairies me semble faisable en effet oui.

gab
gab dit: 18 mai 2010 22:00 Site web

Je confirme, sous IE cela ne fonctionne pas... mais le billet mérite d'être retwitté. Merci

tijesus77
tijesus77 dit: 02 octobre 2010 10:18 Site web

genial c'est pile poil ce qu'il me fallait en fait je cherche a faire un fond d'ecran avec les elements deplaçable.. petit souscis je suis sous linux et comme fond d'ecran il accepte que les fichiers images.. mais je vais voir la chose.. merci encore.. ah oui petite question c'est quoi ces pubs qui viennent? c'est peut-etre pas toi..

Damien
Damien dit: 02 octobre 2010 10:22 Site web

Quelles pub ?? Mis à part les deux Adsense qu'il y a sur cette page, il n'y a aucune autre pub ici.

Germain
Germain dit: 17 novembre 2010 21:02

Merci beaucoup pour ce bon tuto très utile!!

J'ai une petite question : J'ai créé deux listes. Les éléments peuvent ainsi être déplacé d'une liste à l'autre. Par contre lorsque je veux en mettre un au milieu d'une des deux listes, l'élément en question se lie à chaque fois à l'objet du dessus. Pourtant j'ai juste mit 'move' comme 'effectAllowed'. Il existe un moyen pour que ca ne se lie pas??

Encore merci :)

Damien
Damien dit: 18 novembre 2010 08:59 Site web

Je ne suis pas sur de comprendre comment tu peut mettre un élément au milieu de deux listes. Cela signifierait que tu en as 3, la 3e étant celle du milieu :)

Germain
Germain dit: 18 novembre 2010 19:01

Hum oui je me suis mal exprimé désolé. En fait je veux déplacer un élément de la liste 1 dans la liste 2. J'arrive à les déplacer à la fin de la liste mais pas au milieu. Je ne sais pas si je suis encore très clair alors voila: j'ai la liste 1 qui contient A,B,C,D et la liste 2 E,F,G,H. Lorsque je veux mettre B entre F et G, B se "colle" à F. Au lieu de simplement se placer entre.

Voici ma triste histoire :)

Merci!!

Damien
Damien dit: 19 novembre 2010 08:26 Site web

Oui, je vois. C'est assez simple.

Dans la fonction drop(), on fait un appendChild. C'est l'appel à cette méthode qui ajoute l'élément dans la liste. Il suffit de ne pas faire un appendChild, mais de l'ajouter à l'endroit que tu désire.

Avec jQuery, la méthode after() te permettra de faire cela. Il suffit que tu sache après quel élément tu souhaite insérer le tiens. http://api.jquery.com/after/

GERARD
GERARD dit: 29 juillet 2011 17:19

Juste une question: si le drag and drop n'est plus fonctionnel sur windows, comment peut alors le restituer ? Merci de répondre

Yni
Yni dit: 14 août 2011 08:33

Merci pour ce tuto. Dans le cas d'une longue page Web (avec ascenseur vertical), est-il possible de glisser un élément d'une partie visible de la page vers un target qui n'est pas visible ?

(je reposte, car j'ai l'impression que ça n'a pas marché la première fois)

axel
axel dit: 11 octobre 2011 13:43 Site web

Pardon mais la démo marche pas du tout chez moi (Firefox 7)...

axel
axel dit: 11 octobre 2011 13:47 Site web

Up, désolé ça marche très bien en fait. Merci pour le tuto, j'ai un client qui va être content ;-)

Galaxy
Galaxy dit: 03 janvier 2012 02:37

Bonjour,

Et merci pour ce tuto.

On est donc limité au déplacement d'une zone a une autre, avec un 'laché' au hazard dans la zone de réception obligatoirement prédefinie. Impossible donc de placer un élément sur la page avec précision? Ce que nous faisions avec javascript avant HTML5 ;o)

C'est plus simple certes! mais bon...

Merci

lolo pixl
lolo pixl dit: 19 janvier 2012 22:54

Merci Damien pour ce tutorial.

J'ai essayé de résoudre le problème de Germain. Je ne l'ai pas testé sur d'autres navigateurs que Chrome mais ca marche.

J'ai crée plusieurs 'element' et 2 'box':

<div class="box" id="box1" ondragstart="dragStart(event);" ondragover="return dragOver(event);" ondrop="return drop(event);">
<div class="element" id="first" draggable="true">Hey 1</div>
<div class="element" id="second" draggable="true">Hey 2</div>
<div class="element" id="third" draggable="true">Hey 3</div>
</div>
<div class="box" id="box2" ondragstart="dragStart(event);" ondragover="return dragOver(event);" ondrop="return drop(event);">

Et j'ai modifié la fonction de drop ainsi:

function drop(event) {

var element = event.dataTransfer.getData("Text");
if(event.target.className == "box") {
    event.target.appendChild(document.getElementById(element));
}
else if(event.target.className == "element") {
    event.target.parentNode.insertBefore(document.getElementById(element), event.target);
}
event.stopPropagation();
return false;

}

Le principe est simple: - lorsque le drop se fait sur une 'box' je l'ajoute a la liste (appendChild). - lorsque le drop se fait sur un 'element' j'insere mon element avant (insertBefore)

PS pour Galaxy, je pense que le déplacement avec précision peut se faire facilement. Bien que je n'ai pas fait de javascript depuis 2 ans, je pense qu'il ne me faudrait pas plus de 2h pour réussir.

Ginger
Ginger dit: 17 avril 2012 15:10

Bonjour,

J'ai testé tout ça, ça marche super, merci beaucoup Damien !

@lolo pixl : J'ai testé ta fonction drop, qui marche nikel, sauf lorsque l'on rajoute un élément html dans la div draggable.

Ca déplace la div, mais la décale vers la droite et la place au dessus des autres div n'importe comment... Y a t-il un moyen d'empêcher les élements enfants de la div draggable de faire mal s'afficher la div une fois déplacée ?

Voici le code qui marche de départ : http://jsfiddle.net/uYumT/ Et le code problématique : http://jsfiddle.net/vdzYP/1/

Cordialement

Ginger
Ginger dit: 17 avril 2012 15:17

Ps (désolée j'ai oublié une petite question) :

lolo pixl dans ta fonction drop, on peut déplacer la div 2 sur la div 1, mais pas la div 1 sur la div 2, vu que insertBefore() est ici utilisé ;

il faudrait donc voir s'il est possible d'utiliser insertAfter selon l'emplacement des div, afin de pouvoir glisser la div 1 sur la div 2, ou encore la div 1 sur la div 3 (ce sont des exemples).

Ginger
Ginger dit: 17 avril 2012 15:49

Autant pour moi, pour mon deuxième post, il suffit en fait de déplacer la div 1 en dessous la div 2, et non par dessus (donc contrairement à déplacer la div 2 par dessus la div 1) pour que cela se déplace.

Le problème du contenu html dans la div draggable se pose toujours.

Postez un commentaire

Markdown activé