440 votes

HTML5 dragleave déclenché lors du survol d'un élément enfant

Le problème que je rencontre est que le dragleave d'un élément est déclenché lors du survol d'un élément enfant de cet élément. Également, dragenter n'est pas déclenché lorsque l'élément parent est à nouveau survolé.

J'ai fait un violon simplifié : http://jsfiddle.net/pimvdb/HU6Mk/1/ .

HTML :

<div id="drag" draggable="true">drag me</div>

<hr>

<div id="drop">
    drop here
    <p>child</p>
    parent
</div>

avec le JavaScript suivant :

$('#drop').bind({
                 dragenter: function() {
                     $(this).addClass('red');
                 },

                 dragleave: function() {
                     $(this).removeClass('red');
                 }
                });

$('#drag').bind({
                 dragstart: function(e) {
                     e.allowedEffect = "copy";
                     e.setData("text/plain", "test");
                 }
                });

Ce qu'il est censé faire, c'est notifier l'utilisateur en faisant tomber la goutte div rouge lorsque vous y faites glisser quelque chose. Cela fonctionne, mais si vous faites glisser dans le p l'enfant, le dragleave est déclenché et le div n'est plus rouge. Revenons à la goutte div ne le rend pas non plus rouge à nouveau. Il est nécessaire de sortir complètement de la goutte. div et tirez dessus à nouveau pour le rendre rouge.

Est-il possible d'empêcher dragleave de se déclencher lorsqu'il est glissé dans un élément enfant ?

Mise à jour de 2017 : TL;DR, Regardez CSS pointer-events: none; comme décrit dans la réponse de @H.D. ci-dessous qui fonctionne dans les navigateurs modernes et IE11.

0 votes

Le bogue signalé par pimvdb existe toujours dans Webkit depuis mai 2012. Je l'ai contré en ajoutant également une classe dans dragover, ce qui est loin d'être agréable puisqu'il se déclenche si souvent, mais semble corriger un peu le problème.

3 votes

@ajm : Merci, cela fonctionne dans une certaine mesure. Cependant, sur Chrome, il y a un flash lors de l'entrée ou de la sortie de l'élément enfant, vraisemblablement car dragleave est toujours tiré dans ce cas.

0 votes

J'ai ouvert un bug de jQuery UI votes positifs sont les bienvenus afin qu'ils puissent décider d'y consacrer des ressources

467voto

Woody Points 1075

Il vous suffit de conserver un compteur de référence, de l'incrémenter lorsque vous obtenez un dragenter, de le décrémenter lorsque vous obtenez un dragleave. Lorsque le compteur est à 0 - supprimez la classe.

var counter = 0;

$('#drop').bind({
    dragenter: function(ev) {
        ev.preventDefault(); // needed for IE
        counter++;
        $(this).addClass('red');
    },

    dragleave: function() {
        counter--;
        if (counter === 0) { 
            $(this).removeClass('red');
        }
    }
});

Note : Dans l'événement de chute, remettez le compteur à zéro, et effacez la classe ajoutée.

Vous pouvez l'exécuter ici

10 votes

OMG c'est la solution la plus évidente et elle n'a eu qu'UN seul vote... Allez les gens, vous pouvez faire mieux. J'y ai pensé mais après avoir vu le niveau de sophistication des premières réponses, j'ai failli l'écarter. Avez-vous eu des inconvénients ?

0 votes

Bonjour @ArthurCorenzan - merci. Non - cela fonctionne bien, en fait j'ai trouvé une façon encore plus simple de le faire, montrée ci-dessus.

0 votes

Oh non, vous venez de gâcher mon premier commentaire :P Je préférais la solution précédente (empêcher l'apparition de bulles dans l'événement dragleave des éléments enfants).

176voto

H.D. Points 721

Est-il possible d'empêcher le déclenchement du dragleave lors d'un glisser dans un élément enfant ?

Oui.

#drop * {pointer-events: none;}

Ce CSS semble être suffisant pour Chrome.

En l'utilisant avec Firefox, le #drop ne devrait pas avoir de noeuds de texte directement (sinon il y a une étrange question où un élément "se laisse faire" ), je suggère donc de le laisser avec un seul élément (par exemple, utiliser un div à l'intérieur de #drop pour tout mettre dedans).

Voici un jsfiddle résoudre le exemple de question originale (brisée) .

J'ai aussi fait un version simplifiée bifurqué de l'exemple de @Theodore Brown, mais basé uniquement sur ce CSS.

Ce CSS n'est cependant pas implémenté dans tous les navigateurs : http://caniuse.com/pointer-events

En regardant le code source de Facebook, j'ai pu trouver ceci pointer-events: none; plusieurs fois, mais il est probablement utilisé avec des solutions de repli en cas de dégradation progressive. Au moins, c'est si simple et cela résout le problème pour beaucoup d'environnements.

0 votes

La propriété pointer-events est la bonne solution pour l'avenir, mais malheureusement elle ne fonctionne pas dans IE8-IE10, qui sont encore largement utilisés. De plus, je dois souligner que votre jsFiddle actuel ne fonctionne même pas dans IE11, puisqu'il n'ajoute pas les écouteurs d'événements nécessaires et la prévention du comportement par défaut.

3 votes

L'utilisation de pointer-events est en effet une bonne réponse, j'ai lutté un peu avant de découvrir par moi-même, que la réponse devrait être plus élevée.

0 votes

Une option fantastique pour ceux d'entre nous qui ont la chance de ne pas avoir à prendre en charge d'anciens navigateurs.

60voto

pk7 Points 185

Voici la solution Cross-Browser la plus simple (sérieusement) :

jsfiddle <-- essayez de faire glisser un fichier à l'intérieur de la boîte

Vous pouvez faire quelque chose comme ça :

var dropZone= document.getElementById('box');
var dropMask = document.getElementById('drop-mask');

dropZone.addEventListener('dragover', drag_over, false);
dropMask.addEventListener('dragleave', drag_leave, false);
dropMask.addEventListener('drop', drag_drop, false);

En quelques mots, vous créez un "masque" à l'intérieur de la zone de dépôt, avec une largeur et une hauteur héritées, une position absolue, qui ne s'affichera que lorsque le glisser-déposer commencera.
Donc, après avoir montré ce masque, vous pouvez faire le tour en attachant les autres événements dragleave & drop sur celui-ci.

Après avoir quitté ou abandonné, il suffit de cacher à nouveau le masque.
Simple et sans complications.

(Obs. : Conseil de Greg Pettit -- Vous devez être sûr que le masque survole la boîte entière, y compris la bordure)

0 votes

Je ne sais pas pourquoi, mais ça ne fonctionne pas de manière cohérente avec Chrome. Parfois, le fait de quitter la zone permet de garder le masque visible.

3 votes

En fait, c'est la frontière. Faites en sorte que le masque chevauche la bordure, sans bordure propre, et cela devrait fonctionner correctement.

1 votes

Remarque, votre jsfiddle comporte un bogue, dans le drag_drop, vous devez supprimer la classe de survol sur "#box" et non sur "#box-a".

37voto

Theodore Brown Points 388

La "bonne" façon de résoudre ce problème est de désactiver les événements de pointeur sur les éléments enfants de la cible de dépôt (comme dans la réponse de @H.D.). Voici un jsFiddle que j'ai créé et qui démontre cette technique . Malheureusement, cela ne fonctionne pas dans les versions d'Internet Explorer antérieures à IE11, puisqu'elles ne prenait pas en charge les événements liés aux pointeurs .

Heureusement, j'ai pu trouver un moyen de contourner le problème. fait fonctionnent dans les anciennes versions d'IE. En gros, il s'agit d'identifier et d'ignorer dragleave qui se produisent lorsque l'on fait glisser un élément enfant. Étant donné que le dragenter est déclenché sur les noeuds enfants avant que l'événement dragleave sur le parent, des écouteurs d'événements distincts peuvent être ajoutés à chaque nœud enfant pour ajouter ou supprimer une classe "ignore-drag-leave" de la cible de dépôt. Ensuite, l'événement dragleave l'auditeur d'événements peut simplement ignorer les appels qui se produisent lorsque cette classe existe. Voici un exemple de jsFiddle démontre cette solution de contournement . Il a été testé et fonctionne dans Chrome, Firefox et IE8+.

Mise à jour :

J'ai créé un jsFiddle démontrant une solution combinée en utilisant la détection des fonctionnalités, où les événements de pointeur sont utilisés s'ils sont pris en charge (actuellement Chrome, Firefox et IE11), et le navigateur revient à l'ajout d'événements aux nœuds enfants si la prise en charge des événements de pointeur n'est pas disponible (IE8-10).

0 votes

Cette réponse présente une solution de contournement pour le déclenchement indésirable, mais néglige entièrement la question "Est-il possible d'empêcher le déclenchement de dragleave lors d'un glissement vers un élément enfant" ?

0 votes

Le comportement peut devenir étrange lorsque l'on fait glisser un fichier depuis l'extérieur du navigateur. Dans Firefox, j'ai obtenu "Entering child -> Entering Parent -> Leaving child -> Entering child -> Leaving child" sans quitter le parent, qui est parti avec la classe "over". L'ancien IE aurait besoin d'un attachEvent pour remplacer le addEventListener.

0 votes

Cette solution dépend fortement du bullage, le "false" dans tous les addEventListener devrait être souligné comme essentiel (bien que ce soit le comportement par défaut), car beaucoup de gens peuvent ne pas le savoir.

7voto

Aldekein Points 408

Et voilà, une solution pour Chrome :

.bind('dragleave', function(event) {
                    var rect = this.getBoundingClientRect();
                    var getXY = function getCursorPosition(event) {
                        var x, y;

                        if (typeof event.clientX === 'undefined') {
                            // try touch screen
                            x = event.pageX + document.documentElement.scrollLeft;
                            y = event.pageY + document.documentElement.scrollTop;
                        } else {
                            x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
                            y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
                        }

                        return { x: x, y : y };
                    };

                    var e = getXY(event.originalEvent);

                    // Check the mouseEvent coordinates are outside of the rectangle
                    if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) {
                        console.log('Drag is really out of area!');
                    }
                })

0 votes

Est-ce que ça va dans "if (typeof event.clientX === 'undefined')" ?

1 votes

Cela a bien fonctionné, mais il peut y avoir une autre fenêtre au-dessus du navigateur, donc obtenir la position de la souris et la comparer à la zone rectangulaire de l'écran n'est pas suffisant.

0 votes

Je suis d'accord avec @H.D. De plus, cela posera des problèmes lorsque l'élément aura de grandes quantités de nourriture. border-radius comme je l'ai expliqué dans mon commentaire sur la réponse de @azlar ci-dessus.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X