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

301

Le problème que j'ai est que l' dragleaveévénement d'un élément est déclenché lors du survol d'un élément enfant de cet élément. En outre, dragentern'est pas déclenché lors du survol de l'élément parent.

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 d'avertir l'utilisateur en rendant le drop divrouge quand il y fait glisser quelque chose. Cela fonctionne, mais si vous glissez dans l' penfant, le dragleaveest tiré et divn'est plus rouge. Revenir à la goutte divne la rend pas non plus rouge. Il est nécessaire de sortir complètement de la goutte divet de la faire glisser à nouveau pour la rendre rouge.

Est-il possible d'empêcher le dragleavetir lors du glissement dans un élément enfant?

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

pimvdb
la source
Le bogue pimvdb signalé existe toujours dans Webkit en mai 2012. Je l'ai contré en ajoutant également une classe dans dragover, ce qui n'est pas du tout agréable car il se déclenche si souvent, mais semble corriger un peu le problème.
ajm
2
@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, probablement parce qu'il dragleaveest toujours déclenché dans ce cas.
pimvdb
J'ai ouvert un bug de jQuery UI upvotes sont les bienvenus pour qu'ils puissent décider d'y mettre des ressources
fguillen
@fguillen: Je suis désolé mais cela n'a rien à voir avec l'interface utilisateur jQuery. En fait, jQuery n'est même pas nécessaire pour déclencher le bogue. J'ai déjà déposé un bogue WebKit mais il n'y a pas de mise à jour pour l'instant.
pimvdb
2
@pimvdb Pourquoi n'acceptez-vous pas la réponse de HD?
TylerH

Réponses:

339

Il vous suffit de conserver un compteur de référence, de l'incrémenter lorsque vous obtenez un dragenter, de 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');
        }
    }
});

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

Vous pouvez l'exécuter ici

Boisé
la source
8
OMG c'est la solution la plus évidente et il n'y avait qu'un seul vote ... Allez les gens, vous pouvez faire mieux. J'y pensais mais après avoir vu le niveau de sophistication des premières réponses, je l'ai presque rejeté. Avez-vous eu des inconvénients?
Arthur Corenzan
11
Cela n'a pas fonctionné lorsque le bord de l'élément déplacé touche le bord d'un autre élément déplaçable. Par exemple, une liste triable; faire glisser l'élément vers le bas, sur l'élément déplaçable suivant ne décrémente pas le compteur à 0, au lieu de cela, il est bloqué à 1. Mais si je le fais glisser latéralement, hors de tout autre élément déplaçable, cela fonctionne. Je suis allé avec pointer-events: noneles enfants. Lorsque je commence à faire glisser, j'ajoute une classe qui a cette propriété, lorsque le glisser est terminé, je supprime la classe. A bien fonctionné sur Safari et Chrome, mais pas sur Firefox.
Arthur Corenzan
13
Cela a fonctionné pour moi dans tous les navigateurs, mais Firefox. J'ai une nouvelle solution qui fonctionne partout dans mon cas. Le premier, dragenterj'enregistre event.currentTargetdans une nouvelle variable dragEnterTarget. Tant qu'il dragEnterTargetest défini, j'ignore les autres dragenterévénements, car ils proviennent d'enfants. Dans tous les dragleavecas, je vérifie dragEnterTarget === event.target. Si c'est faux, l'événement sera ignoré car il a été déclenché par un enfant. Si cela est vrai, je reviens dragEnterTargetà undefined.
Pipo
3
Excellente solution. J'ai dû réinitialiser le compteur à 0 dans la fonction qui gère l'acceptation de la suppression, sinon les traînées suivantes ne fonctionnaient pas comme prévu.
Liam Johnston
2
@Woody Je n'ai pas vu son commentaire dans l'énorme liste de commentaires ici, mais oui, c'est la solution. Pourquoi ne pas l'intégrer dans votre réponse?
BT
93

Est-il possible d'empêcher le dragleave de se déclencher lors du glissement 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 nœuds de texte directement (sinon il y a un problème étrange où un élément "le laisse à lui-même" ), donc je suggère de le laisser avec un seul élément (par exemple, utilisez un div à l'intérieur # goutte pour tout mettre à l'intérieur)

Voici un jsfiddle résolvant l'exemple de question d'origine (cassé) .

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

Cependant, tous les navigateurs n'ont pas ce CSS implémenté: http://caniuse.com/pointer-events

En voyant le code source de Facebook, j'ai pu le trouver pointer-events: none;plusieurs fois, mais il est probablement utilisé avec des retombées de dégradation gracieuses. Au moins, c'est si simple et résout le problème pour de nombreux environnements.

HD
la source
La propriété pointer-events est la bonne solution à 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, car il n'ajoute pas les écouteurs d'événements nécessaires et la prévention de comportement par défaut.
Theodore Brown
2
Utiliser des événements de pointeur est en effet une bonne réponse, j'ai lutté un peu avant de découvrir par moi-même, cette réponse devrait être plus élevée.
floribon
Une option fantastique pour ceux d'entre nous qui ont la chance de ne pas avoir à prendre en charge les anciens navigateurs.
Luke Hansford
7
Et si vos enfants sont un bouton? oO
David
9
cela ne fonctionne que si vous n'avez pas d'autres éléments de contrôleur (modifier, supprimer) à l'intérieur de la zone, car cette solution les bloque aussi ..
Zoltán Süle
45

Ici, 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 s'affichera au début du glisser.
Ainsi, après avoir montré ce masque, vous pouvez faire l'affaire en y attachant les autres événements glisser-déposer.

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

(Obs .: conseil de Greg Pettit - Vous devez être sûr que le masque plane sur toute la boîte, y compris la bordure)

Diego T. Yamaguchi
la source
Je ne sais pas pourquoi, mais cela ne fonctionne pas de manière cohérente avec Chrome. Parfois, quitter la zone garde le masque visible.
Greg Pettit
2
En fait, c'est la frontière. Demandez au masque de chevaucher la bordure, sans bordure propre, et cela devrait fonctionner correctement.
Greg Pettit
1
Remarque, votre jsfiddle contient un bug, dans drag_drop, vous devez supprimer la classe de survol sur "#box" et non "# box-a"
entropie
C'est une bonne solution, mais d'une manière ou d'une autre, cela n'a pas fonctionné pour moi. Je trouve enfin une solution de contournement. Pour ceux d'entre vous qui recherchent autre chose. vous pouvez essayer ceci: github.com/bingjie2680/jquery-draghover
bingjie2680
Non, je ne veux pas perdre le contrôle des éléments enfants. J'ai créé un simple plugin jQuery qui résout le problème de faire le travail pour nous. Vérifiez ma réponse .
Luca Fagioli
39

Cela fait un certain temps que cette question est posée et de nombreuses solutions (y compris des hacks laids) sont fournies.

J'ai réussi à résoudre le même problème que j'avais récemment grâce à la réponse dans cette réponse et j'ai pensé qu'il pourrait être utile à quelqu'un qui accède à cette page. L'idée est de stocker le evenet.targetà ondrageenterchaque fois qu'il est appelé sur l'un des éléments parents ou enfants. Ensuite, ondragleavevérifiez si la cible actuelle ( event.target) est égale à l'objet dans lequel vous avez stockéondragenter .

Le seul cas où ces deux sont identiques est lorsque votre glisser quitte la fenêtre du navigateur.

La raison pour laquelle cela fonctionne bien est lorsque la souris quitte un élément (par exemple el1) et entre dans un autre élément (par exemple el2), d'abord le el2.ondragenterest appelé, puis el1.ondragleave. Ce n'est que lorsque le glisser quitte / entre dans la fenêtre du navigateur, event.targetsera ''dans les deux el2.ondragenter et el1.ondragleave.

Voici mon échantillon de travail. Je l'ai testé sur IE9 +, Chrome, Firefox et Safari.

(function() {
    var bodyEl = document.body;
    var flupDiv = document.getElementById('file-drop-area');

    flupDiv.onclick = function(event){
        console.log('HEy! some one clicked me!');
    };

    var enterTarget = null;

    document.ondragenter = function(event) {
        console.log('on drag enter: ' + event.target.id);
        enterTarget = event.target;
        event.stopPropagation();
        event.preventDefault();
        flupDiv.className = 'flup-drag-on-top';
        return false;
    };

    document.ondragleave = function(event) {
        console.log('on drag leave: currentTarget: ' + event.target.id + ', old target: ' + enterTarget.id);
        //Only if the two target are equal it means the drag has left the window
        if (enterTarget == event.target){
            event.stopPropagation();
            event.preventDefault();
            flupDiv.className = 'flup-no-drag';         
        }
    };
    document.ondrop = function(event) {
        console.log('on drop: ' + event.target.id);
        event.stopPropagation();
        event.preventDefault();
        flupDiv.className = 'flup-no-drag';
        return false;
    };
})();

Et voici une simple page html:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Multiple File Uploader</title>
<link rel="stylesheet" href="my.css" />
</head>
<body id="bodyDiv">
    <div id="cntnr" class="flup-container">
        <div id="file-drop-area" class="flup-no-drag">blah blah</div>
    </div>
    <script src="my.js"></script>
</body>
</html>

Avec un style correct, ce que j'ai fait est d'agrandir la div intérieure (# file-drop-area) chaque fois qu'un fichier est glissé dans l'écran afin que l'utilisateur puisse facilement déposer les fichiers au bon endroit.

Ali Motevallian
la source
4
C'est la meilleure solution, c'est mieux que le compteur (surtout si vous déléguez des événements) et cela fonctionne aussi avec les enfants déplaçables.
Fred
3
Cela ne résout pas le problème des événements de Dragenter en double
BT
Est-ce que cela fonctionne pour les "enfants" qui sont des pseudo-éléments ou des pseudo-classes CSS? Je ne pouvais pas y arriver, mais peut-être que je me trompais.
Josh Bleecher Snyder
1
Depuis mars 2020, j'ai également eu la meilleure chance avec cette solution. Remarque: certains linters peuvent se plaindre de l'utilisation de ==sur ===. Vous comparez les références d'objet cible d'événement, cela ===fonctionne donc très bien également.
Alex
33

La "bonne" façon de résoudre ce problème consiste à 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 @ HD). Voici un jsFiddle que j'ai créé qui illustre cette technique . Malheureusement, cela ne fonctionne pas dans les versions d'Internet Explorer antérieures à IE11, car elles ne prenaient pas en charge les événements de pointeur .

Heureusement, je suis en mesure de trouver une solution qui fait le travail dans les anciennes versions de IE. Fondamentalement, cela implique d'identifier et d'ignorer les dragleaveévénements qui se produisent lors du glissement sur des éléments enfants. Étant donné que l' dragenterévénement est déclenché sur les nœuds enfants avant l' dragleaveévénement sur le parent, des écouteurs d'événement distincts peuvent être ajoutés à chaque nœud enfant qui ajoutent ou suppriment une classe "ignore-drag-Leave" de la cible de dépôt. Ensuite, l' dragleaveécouteur d'événements de la cible cible peut simplement ignorer les appels qui se produisent lorsque cette classe existe. Voici un jsFiddle démontrant cette solution de contournement . Il est testé et fonctionne dans Chrome, Firefox et IE8 +.

Mettre à jour:

J'ai créé un jsFiddle montrant une solution combinée utilisant la détection de 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 à ajouter des événements aux nœuds enfants si la prise en charge des événements de pointeur n'est pas disponible (IE8 -dix).

Theodore Brown
la source
Cette réponse présente une solution de contournement pour le tir indésirable, mais néglige la question «Est-il possible d'empêcher le dragleave de se déclencher lors du glissement dans un élément enfant? entièrement.
HD
Le comportement peut devenir étrange lorsque vous faites glisser un fichier depuis l'extérieur du navigateur. Dans Firefox, j'ai "Entrée de l'enfant -> Entrée du parent -> Sortie de l'enfant -> Entrée de l'enfant -> Sortie de l'enfant" sans quitter le parent, qui est parti avec la classe "over". L'ancien IE aurait besoin d'un remplacement attachEvent pour l'addEventListener.
HD
Cette solution dépend fortement du bouillonnement, le "faux" dans tous les addEventListener doit être souligné comme essentiel (bien que ce soit le comportement par défaut), car beaucoup de gens ne le savent pas.
HD
Ce glisser-déposer unique ajoute un effet à la zone de dépôt qui n'apparaît pas lorsque la zone de dépôt est utilisée pour d'autres objets déplaçables qui ne déclenchent pas l'événement de démarrage par glisser-déposer. Peut-être que tout utiliser comme dropzone pour l'effet de glisser tout en conservant la vraie dropzone avec d'autres gestionnaires ferait cela.
HD
1
@HD J'ai mis à jour ma réponse avec des informations sur l'utilisation de la propriété CSS pointer-events pour empêcher l' dragleaveévénement de se déclencher dans Chrome, Firefox et IE11 +. J'ai également mis à jour mon autre solution de contournement pour prendre en charge IE8 et IE9, en plus d'IE10. Il est intentionnel que l'effet dropzone ne soit ajouté qu'en faisant glisser le lien "Faites-moi glisser". D'autres peuvent se sentir libres de modifier ce comportement au besoin pour prendre en charge leurs cas d'utilisation.
Theodore Brown
14

Le problème est que l' dragleaveévénement est déclenché lorsque la souris passe devant l'élément enfant.

J'ai essayé différentes méthodes pour vérifier si l' e.targetélément est le même que l' thisélément, mais je n'ai pu obtenir aucune amélioration.

La façon dont j'ai résolu ce problème était un peu un hack, mais fonctionne à 100%.

dragleave: function(e) {
               // Get the location on screen of the element.
               var rect = this.getBoundingClientRect();

               // Check the mouseEvent coordinates are outside of the rectangle
               if(e.x > rect.left + rect.width || e.x < rect.left
               || e.y > rect.top + rect.height || e.y < rect.top) {
                   $(this).removeClass('red');
               }
           }
Greg
la source
1
Merci! Cependant, je ne peux pas faire fonctionner cela dans Chrome. Pourriez-vous fournir un violon de travail de votre hack?
pimvdb le
3
Je pensais à le faire en vérifiant également les coordonnées. Vous avez fait la plupart du travail pour moi, merci :). J'ai cependant dû faire quelques ajustements:if (e.x >= (rect.left + rect.width) || e.x <= rect.left || e.y >= (rect.top + rect.height) || e.y <= rect.top)
Christof
1
Cela ne fonctionnera pas dans Chrome, car son événement n'a pas e.xet e.y.
Hengjie
J'aime cette solution. N'a pas fonctionné dans Firefox en premier. Mais si vous remplacez ex par e.clientX et ey par e.clientY cela fonctionne. Fonctionne également dans Chrome.
Nicole Stutz
Je n'ai pas travaillé en chrome pour moi, ni ce que Chris ou Daniel Stuts ont suggéré
Timo Huovinen
14

si vous utilisez HTML5, vous pouvez obtenir le clientRect du parent:

let rect = document.getElementById("drag").getBoundingClientRect();

Puis dans le parent.dragleave ():

dragleave(e) {
    if(e.clientY < rect.top || e.clientY >= rect.bottom || e.clientX < rect.left || e.clientX >= rect.right) {
        //real leave
    }
}

voici un jsfiddle

azlar
la source
2
Excellente réponse.
broc.seib
Merci mon pote, j'aime bien l'approche javascript.
Tim Gerhard
Lorsque vous utilisez des éléments ayant border-radius, déplacer le pointeur près du coin laisserait en fait l'élément, mais ce code penserait toujours que nous sommes à l'intérieur (nous avons laissé l'élément mais nous sommes toujours dans le rectangle englobant). Ensuite, le dragleavegestionnaire d'événements ne sera pas appelé du tout.
Jay Dadhania
11

Une solution très simple consiste à utiliser la pointer-eventspropriété CSS . Définissez simplement sa valeur nonesur dragstart sur chaque élément enfant. Ces éléments ne déclencheront plus d'événements liés à la souris, ils n'attraperont donc pas la souris dessus et ne déclencheront donc pas le glisser - déposer sur le parent.

N'oubliez pas de redéfinir cette propriété à la autofin du glissement;)

FruityFred
la source
11

Cette solution assez simple fonctionne pour moi jusqu'à présent, en supposant que votre événement est attaché à chaque élément de glissement individuellement.

if (evt.currentTarget.contains(evt.relatedTarget)) {
  return;
}
Kenneth Spencer
la source
C'est bien! merci d'avoir partagé @kenneth, vous m'avez sauvé la journée! La plupart des autres réponses ne s'appliquent que si vous avez une seule zone largable.
antoni
Pour moi, cela fonctionne dans Chrome et Firefox, mais id ne fonctionne pas dans Edge. Veuillez voir: jsfiddle.net/iwiss/t9pv24jo
iwis
8

Solution très simple:

parent.addEventListener('dragleave', function(evt) {
    if (!parent.contains(evt.relatedTarget)) {
        // Here it is only dragleave on the parent
    }
}
Owen M
la source
Cela ne semble pas fonctionner dans Safari 12.1: jsfiddle.net/6d0qc87m
Adam Taylor
7

Vous pouvez le corriger dans Firefox avec un peu d'inspiration du code source jQuery :

dragleave: function(e) {
    var related = e.relatedTarget,
        inside = false;

    if (related !== this) {

        if (related) {
            inside = jQuery.contains(this, related);
        }

        if (!inside) {

            $(this).removeClass('red');
        }
    }

}

Malheureusement, cela ne fonctionne pas dans Chrome car il relatedTargetsemble ne pas exister sur les dragleaveévénements, et je suppose que vous travaillez dans Chrome parce que votre exemple ne fonctionnait pas dans Firefox. Voici une version avec le code ci-dessus implémenté.

robertc
la source
Merci beaucoup, mais en effet c'est Chrome dans
lequel
5
@pimvdb Je vois que vous avez enregistré un bug , je vais juste laisser une référence ici au cas où quelqu'un d'autre trouverait cette réponse.
robertc
Je l'ai fait en effet, mais j'ai oublié d'ajouter un lien ici. Merci de l'avoir fait.
pimvdb
Le bogue Chrome a été corrigé entre-temps.
phk
7

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!');
                    }
                })
Aldekein
la source
Va-t-il dans «if (typeof event.clientX === 'undefined')»?
Aldekein
1
A bien fonctionné mais il pourrait y avoir une autre fenêtre sur le navigateur, donc obtenir l'emplacement de la souris et le comparer à la zone d'écran rectangulaire n'est pas suffisant.
HD
Je suis d'accord avec @HD En outre, cela posera des problèmes lorsque l'élément est volumineux, border-radiuscomme je l'ai expliqué dans mon commentaire sur la réponse de @ azlar ci-dessus.
Jay Dadhania
7

Voici une autre solution utilisant document.elementFromPoint:

 dragleave: function(event) {
   var event = event.originalEvent || event;
   var newElement = document.elementFromPoint(event.pageX, event.pageY);
   if (!this.contains(newElement)) {
     $(this).removeClass('red');
   }
}

J'espère que cela fonctionne, voici un violon .

marcelj
la source
3
Cela n'a pas fonctionné pour moi hors de la boîte. J'avais besoin d'utiliser à la event.clientX, event.clientYplace, car ils sont relatifs à la fenêtre et non à la page. J'espère que cela aide une autre âme perdue là-bas.
Jon Abrams
7

Une solution simple consiste à ajouter la règle css pointer-events: noneau composant enfant pour empêcher le déclenchement de ondragleave. Voir l'exemple:

function enter(event) {
  document.querySelector('div').style.border = '1px dashed blue';
}

function leave(event) {
  document.querySelector('div').style.border = '';
}
div {
  border: 1px dashed silver;
  padding: 16px;
  margin: 8px;
}

article {
  border: 1px solid silver;
  padding: 8px;
  margin: 8px;
}

p {
  pointer-events: none;
  background: whitesmoke;
}
<article draggable="true">drag me</article>

<div ondragenter="enter(event)" ondragleave="leave(event)">
  drop here
  <p>child not triggering dragleave</p>
</div>

Alexandre Annic
la source
J'ai eu ce même problème et votre solution fonctionne très bien. Astuce pour les autres, c'était mon cas: si l'élément enfant que vous essayez de faire ignorer l' événement de glissement est le clone de l' élément que vous faites glisser (en essayant d'obtenir un aperçu visuel), vous pouvez l'utiliser dans dragstart lors de la création du clone :dragged = event.target;clone = dragged.cloneNode();clone.style.pointerEvents = 'none';
Scaramouche
4

Je ne sais pas si ce navigateur croisé, mais j'ai testé dans Chrome et cela résout mon problème:

Je veux faire glisser et déposer un fichier sur toute la page, mais mon dragleave est déclenché lorsque je fais glisser sur l'élément enfant. Ma solution était de regarder les x et y de la souris:

j'ai un div qui recouvre toute ma page, lorsque la page se charge je la cache.

lorsque vous faites glisser le document, je le montre, et lorsque vous déposez sur le parent, il le gère, et lorsque vous quittez le parent, je vérifie x et y.

$('#draganddrop-wrapper').hide();

$(document).bind('dragenter', function(event) {
    $('#draganddrop-wrapper').fadeIn(500);
    return false;
});

$("#draganddrop-wrapper").bind('dragover', function(event) {
    return false;
}).bind('dragleave', function(event) {
    if( window.event.pageX == 0 || window.event.pageY == 0 ) {
        $(this).fadeOut(500);
        return false;
    }
}).bind('drop', function(event) {
    handleDrop(event);

    $(this).fadeOut(500);
    return false;
});
chrisallick
la source
1
Hacky mais intelligent. J'aime. A travaillé pour moi.
cupcakekid
1
Oh, comme je souhaite que cette réponse ait plus de votes! Merci
Kirby
4

J'ai écrit une petite bibliothèque appelée Dragster pour gérer ce problème exact, fonctionne partout sauf ne rien faire en silence dans IE (qui ne prend pas en charge les constructeurs d'événements DOM, mais il serait assez facile d'écrire quelque chose de similaire en utilisant les événements personnalisés de jQuery)

Ben
la source
Très utile (du moins pour moi où je ne me soucie que de Chrome).
M Katz
4

J'avais le même problème et j'ai essayé d'utiliser la solution pk7s. Cela a fonctionné mais cela pourrait être fait un peu mieux sans aucun élément dom supplémentaire.

Fondamentalement, l'idée est la même: ajoutez une superposition invisible supplémentaire sur la zone largable. Permet uniquement de le faire sans aucun élément dom supplémentaire. Voici la partie où les pseudo-éléments CSS sont venus jouer.

Javascript

var dragOver = function (e) {
    e.preventDefault();
    this.classList.add('overlay');
};

var dragLeave = function (e) {
    this.classList.remove('overlay');
};


var dragDrop = function (e) {
    this.classList.remove('overlay');
    window.alert('Dropped');
};

var dropArea = document.getElementById('box');

dropArea.addEventListener('dragover', dragOver, false);
dropArea.addEventListener('dragleave', dragLeave, false);
dropArea.addEventListener('drop', dragDrop, false);

CSS

Cette règle après créera une superposition entièrement couverte pour la zone largable.

#box.overlay:after {
    content:'';
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    z-index: 1;
}

Voici la solution complète: http://jsfiddle.net/F6GDq/8/

J'espère que cela aide toute personne ayant le même problème.

rasmusx
la source
1
Parfois, ne fonctionne pas sur le chrome (n'accroche pas correctement la traînée)
Timo Huovinen
4

Vérifiez simplement si l'élément glissé est un enfant, s'il l'est, puis ne supprimez pas votre classe de style «glisser». Assez simple et fonctionne pour moi:

 $yourElement.on('dragleave dragend drop', function(e) {
      if(!$yourElement.has(e.target).length){
           $yourElement.removeClass('is-dragover');
      }
  })
sofarsoghood
la source
Cela ressemble à la solution la plus simple pour moi, et cela a résolu mon problème. Merci!
Francesco Marchetti-Stasi
3

Je suis tombé sur le même problème et voici ma solution - qui je pense est beaucoup plus facile que ci-dessus. Je ne suis pas sûr que ce soit un croisement (cela pourrait dépendre de l'ordre de propagation même)

Je vais utiliser jQuery pour plus de simplicité, mais la solution doit être indépendante du framework.

L'événement bouillonne pour être parent dans les deux sens, étant donné:

<div class="parent">Parent <span>Child</span></div>

Nous attachons des événements

el = $('.parent')
setHover = function(){ el.addClass('hovered') }
onEnter  = function(){ setTimeout(setHover, 1) }
onLeave  = function(){ el.removeClass('hovered') } 
$('.parent').bind('dragenter', onEnter).bind('dragleave', onLeave)

Et c'est tout. :) cela fonctionne parce que même si onEnter sur les incendies d'enfant avant onLeave sur le parent, nous le retardons légèrement en inversant l'ordre, donc la classe est supprimée d'abord puis réappliquée après une milliseconde.

Marcin Raczkowski
la source
La seule chose que cet extrait de code fait est d'empêcher la classe «survolée» d'être supprimée en la réappliquant au cycle de tick suivant (ce qui rend l'événement «dragleave» inutile).
null
Ce n'est pas inutile. Si vous quittez le parent, cela fonctionnera comme prévu. La puissance de cette solution est sa simplicité, elle n'est pas idéale ou meilleure. La meilleure solution serait de marquer une entrée sur un enfant, en vérifiant en continu si nous venons d'entrer l'enfant, et si tel n'est pas le cas, déclencher un événement de congé. Il n'aura cependant pas besoin de tests, de gardes supplémentaires, de contrôles pour les petits-enfants, etc.
Marcin Raczkowski
3

J'ai écrit un module de glisser-déposer appelé goutte-à-goutte qui corrige ce comportement bizarre, entre autres. Si vous cherchez un bon module de glisser-déposer de bas niveau que vous pouvez utiliser comme base pour tout (téléchargement de fichiers, glisser-déposer dans l'application, glisser depuis ou vers des sources externes), vous devriez le vérifier module out:

https://github.com/fresheneesz/drip-drop

Voici comment vous feriez ce que vous essayez de faire en goutte à goutte:

$('#drop').each(function(node) {
  dripDrop.drop(node, {
    enter: function() {
      $(node).addClass('red')  
    },
    leave: function() {
      $(node).removeClass('red')
    }
  })
})
$('#drag').each(function(node) {
  dripDrop.drag(node, {
    start: function(setData) {
      setData("text", "test") // if you're gonna do text, just do 'text' so its compatible with IE's awful and restrictive API
      return "copy"
    },
    leave: function() {
      $(node).removeClass('red')
    }
  })
})

Pour ce faire, sans bibliothèque, la technique du compteur est ce que j'ai utilisé dans le goutte-à-goutte, la réponse la mieux notée manque des étapes importantes qui entraîneront des ruptures pour tout sauf la première goutte. Voici comment procéder correctement:

var counter = 0;    
$('#drop').bind({
    dragenter: function(ev) {
        ev.preventDefault()
        counter++
        if(counter === 1) {
          $(this).addClass('red')
        }
    },

    dragleave: function() {
        counter--
        if (counter === 0) { 
            $(this).removeClass('red');
        }
    },
    drop: function() {
        counter = 0 // reset because a dragleave won't happen in this case
    }
});
BT
la source
2

Une solution de travail alternative, un peu plus simple.

//Note: Due to a bug with Chrome the 'dragleave' event is fired when hovering the dropzone, then
//      we must check the mouse coordinates to be sure that the event was fired only when 
//      leaving the window.
//Facts:
//  - [Firefox/IE] e.originalEvent.clientX < 0 when the mouse is outside the window
//  - [Firefox/IE] e.originalEvent.clientY < 0 when the mouse is outside the window
//  - [Chrome/Opera] e.originalEvent.clientX == 0 when the mouse is outside the window
//  - [Chrome/Opera] e.originalEvent.clientY == 0 when the mouse is outside the window
//  - [Opera(12.14)] e.originalEvent.clientX and e.originalEvent.clientY never get
//                   zeroed if the mouse leaves the windows too quickly.
if (e.originalEvent.clientX <= 0 || e.originalEvent.clientY <= 0) {
Profet
la source
Cela ne semble pas toujours fonctionner dans Chrome. Je recevrais parfois clientXau-dessus de 0 lorsque la souris est en dehors de la boîte. Certes, mes éléments sontposition:absolute
Hengjie
Cela arrive-t-il tout le temps ou seulement parfois? Parce que si la souris se déplace trop vite (par exemple en dehors de la fenêtre), vous risquez d'obtenir des valeurs erronées.
Profet
Cela arrive 90% du temps. Il y a de rares cas (1 fois sur 10) où je peux le faire atteindre 0. J'essaierai à nouveau de déplacer la souris plus lentement, mais je ne pourrais pas dire que je me déplaçais rapidement (peut-être ce que vous appelleriez une vitesse normale).
Hengjie
2

Après avoir passé tant d'heures, cette suggestion a fonctionné exactement comme prévu. Je voulais fournir un signal uniquement lorsque les fichiers étaient glissés et que le glisser-déplacer du document entraînait des scintillements douloureux sur le navigateur Chrome.

C'est ainsi que je l'ai résolu, en insérant également des indices appropriés pour l'utilisateur.

$(document).on('dragstart dragenter dragover', function(event) {    
    // Only file drag-n-drops allowed, http://jsfiddle.net/guYWx/16/
    if ($.inArray('Files', event.originalEvent.dataTransfer.types) > -1) {
        // Needed to allow effectAllowed, dropEffect to take effect
        event.stopPropagation();
        // Needed to allow effectAllowed, dropEffect to take effect
        event.preventDefault();

        $('.dropzone').addClass('dropzone-hilight').show();     // Hilight the drop zone
        dropZoneVisible= true;

        // http://www.html5rocks.com/en/tutorials/dnd/basics/
        // http://api.jquery.com/category/events/event-object/
        event.originalEvent.dataTransfer.effectAllowed= 'none';
        event.originalEvent.dataTransfer.dropEffect= 'none';

         // .dropzone .message
        if($(event.target).hasClass('dropzone') || $(event.target).hasClass('message')) {
            event.originalEvent.dataTransfer.effectAllowed= 'copyMove';
            event.originalEvent.dataTransfer.dropEffect= 'move';
        } 
    }
}).on('drop dragleave dragend', function (event) {  
    dropZoneVisible= false;

    clearTimeout(dropZoneTimer);
    dropZoneTimer= setTimeout( function(){
        if( !dropZoneVisible ) {
            $('.dropzone').hide().removeClass('dropzone-hilight'); 
        }
    }, dropZoneHideDelay); // dropZoneHideDelay= 70, but anything above 50 is better
});
visitesb
la source
1

J'ai eu un problème similaire: mon code pour masquer la zone de dépôt lors de l'événement dragleave pour le corps a été déclenché de manière contagieuse lorsque vous survoliez des éléments enfants, ce qui faisait scintiller la zone de dépôt dans Google Chrome.

J'ai pu résoudre ce problème en planifiant la fonction de masquage de la zone de dépôt au lieu de l'appeler immédiatement. Ensuite, si un autre glisser-déplacer ou glisser-déplacer est déclenché, l'appel de fonction planifié est annulé.

body.addEventListener('dragover', function() {
    clearTimeout(body_dragleave_timeout);
    show_dropzone();
}, false);

body.addEventListener('dragleave', function() {
    clearTimeout(body_dragleave_timeout);
    body_dragleave_timeout = setTimeout(show_upload_form, 100);
}, false);

dropzone.addEventListener('dragover', function(event) {
    event.preventDefault();
    dropzone.addClass("hover");
}, false);

dropzone.addEventListener('dragleave', function(event) {
    dropzone.removeClass("hover");
}, false);
Arseny
la source
C'est ce que j'ai fini par faire aussi, mais c'est encore sommaire.
mpen
1

L' événement " dragleave " est déclenché lorsque le pointeur de la souris quitte la zone de déplacement du conteneur cible.

Ce qui a beaucoup de sens car dans de nombreux cas, seul le parent peut être largable et non les descendants. Je pense que event.stopPropogation () aurait dû gérer ce cas mais semble ne pas faire l'affaire.

Ci-dessus, certaines solutions semblent fonctionner pour la plupart des cas, mais échouent dans le cas des enfants qui ne prennent pas en charge les événements dragenter / dragleave , tels que iframe .

Une solution consiste à vérifier l' événement event.relatedTarget et à vérifier s'il se trouve à l'intérieur du conteneur, puis à ignorer l' événement dragleave comme je l'ai fait ici:

function isAncestor(node, target) {
    if (node === target) return false;
    while(node.parentNode) {
        if (node.parentNode === target)
            return true;
        node=node.parentNode;
    }
    return false;
}

var container = document.getElementById("dropbox");
container.addEventListener("dragenter", function() {
    container.classList.add("dragging");
});

container.addEventListener("dragleave", function(e) {
    if (!isAncestor(e.relatedTarget, container))
        container.classList.remove("dragging");
});

Vous pouvez trouver un violon qui fonctionne ici !

Lalit Nankani
la source
1

Résolu ..!

Déclarez n'importe quel tableau par exemple:

targetCollection : any[] 

dragenter: function(e) {
    this.targetCollection.push(e.target); // For each dragEnter we are adding the target to targetCollection 
    $(this).addClass('red');
},

dragleave: function() {
    this.targetCollection.pop(); // For every dragLeave we will pop the previous target from targetCollection
    if(this.targetCollection.length == 0) // When the collection will get empty we will remove class red
    $(this).removeClass('red');
}

Pas besoin de s'inquiéter des éléments enfants.

Shubham Rajodiya
la source
1

J'ai beaucoup lutté avec cela, même après avoir lu toutes ces réponses, et j'ai pensé que je pourrais partager ma solution avec vous, parce que je pensais que ce pourrait être l'une des approches les plus simples, quelque peu différente cependant. Ma pensée était simplement d'omettre dragleavecomplètement l'écouteur d'événements et de coder le comportement de glisser-déplacer avec chaque nouvel événement dragenter déclenché, tout en s'assurant que les événements dragenter ne soient pas déclenchés inutilement.

Dans mon exemple ci-dessous, j'ai une table, où je veux pouvoir échanger le contenu des lignes de table entre elles via l'API glisser-déposer. Le dragenter, une classe CSS doit être ajoutée à l'élément de ligne dans lequel vous faites actuellement glisser votre élément, pour le mettre en surbrillance, et sur dragleave, cette classe doit être supprimée.

Exemple:

Tableau HTML très basique:

<table>
  <tr>
    <td draggable="true" class="table-cell">Hello</td>
  </tr>
  <tr>
    <td draggable="true" clas="table-cell">There</td>
  </tr>
</table>

Et la fonction de gestionnaire d'événements dragenter, a ajouté sur chaque cellule de table ( à part dragstart, dragover, dropet les dragendgestionnaires qui ne sont pas spécifiques à cette question, donc pas copiés ici):

/*##############################################################################
##                              Dragenter Handler                             ##
##############################################################################*/

// When dragging over the text node of a table cell (the text in a table cell),
// while previously being over the table cell element, the dragleave event gets
// fired, which stops the highlighting of the currently dragged cell. To avoid
// this problem and any coding around to fight it, everything has been
// programmed with the dragenter event handler only; no more dragleave needed

// For the dragenter event, e.target corresponds to the element into which the
// drag enters. This fact has been used to program the code as follows:

var previousRow = null;

function handleDragEnter(e) {
  // Assure that dragenter code is only executed when entering an element (and
  // for example not when entering a text node)
  if (e.target.nodeType === 1) {
    // Get the currently entered row
    let currentRow = this.closest('tr');
    // Check if the currently entered row is different from the row entered via
    // the last drag
    if (previousRow !== null) {
      if (currentRow !== previousRow) {
        // If so, remove the class responsible for highlighting it via CSS from
        // it
        previousRow.className = "";
      }
    }
    // Each time an HTML element is entered, add the class responsible for
    // highlighting it via CSS onto its containing row (or onto itself, if row)
    currentRow.className = "ready-for-drop";
    // To know which row has been the last one entered when this function will
    // be called again, assign the previousRow variable of the global scope onto
    // the currentRow from this function run
    previousRow = currentRow;
  }
}

Commentaires très basiques laissés dans le code, tels que ce code convient aussi aux débutants. Espérons que cela va vous aider! Notez que vous devrez bien sûr ajouter tous les écouteurs d'événement que j'ai mentionnés ci-dessus sur chaque cellule du tableau pour que cela fonctionne.

DevelJoe
la source
0

Voici une autre approche basée sur le calendrier des événements.

L' dragenterévénement distribué à partir de l'élément enfant peut être capturé par l'élément parent et il se produit toujours avant le dragleave. Le délai entre ces deux événements est vraiment court, plus court que toute action possible de la souris humaine. Donc, l'idée est de mémoriser le moment où un événement se dragenterproduit et de filtrer les dragleaveévénements qui se produisent "pas trop rapidement" après ...

Ce court exemple fonctionne sur Chrome et Firefox:

var node = document.getElementById('someNodeId'),
    on   = function(elem, evt, fn) { elem.addEventListener(evt, fn, false) },
    time = 0;

on(node, 'dragenter', function(e) {
    e.preventDefault();
    time = (new Date).getTime();
    // Drag start
})

on(node, 'dragleave', function(e) {
    e.preventDefault();
    if ((new Date).getTime() - time > 5) {
         // Drag end
    }
})
smrtl
la source
0

pimvdb ..

Pourquoi n'essayez-vous pas d'utiliser drop au lieu de dragleave . Ça a marché pour moi. J'espère que ceci résoudra votre problème.

Veuillez vérifier le jsFiddle: http://jsfiddle.net/HU6Mk/118/

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

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

$('#drag').bind({
                 dragstart: function(e) {
                     e.allowedEffect = "copy";
                     e.setData("text/plain", "test");
                 }
                });
abhi
la source
2
Si l'utilisateur change d'avis et fait glisser le fichier hors du navigateur, cela restera rouge.
ZachB
0

Vous pouvez utiliser un délai d'expiration avec un transitioningindicateur et écouter l'élément supérieur. dragenter/ dragleavedes événements enfants bouillonnent jusqu'au conteneur.

Étant donné que dragenterl'élément enfant se déclenche avant dragleavele conteneur, nous définirons l'indicateur comme transition pendant 1 ms ... l' dragleaveauditeur vérifiera l'indicateur avant que le 1 ms ne soit écoulé.

L'indicateur ne sera vrai que pendant les transitions vers les éléments enfants et ne sera pas vrai lors de la transition vers un élément parent (du conteneur)

var $el = $('#drop-container'),
    transitioning = false;

$el.on('dragenter', function(e) {

  // temporarily set the transitioning flag for 1 ms
  transitioning = true;
  setTimeout(function() {
    transitioning = false;
  }, 1);

  $el.toggleClass('dragging', true);

  e.preventDefault();
  e.stopPropagation();
});

// dragleave fires immediately after dragenter, before 1ms timeout
$el.on('dragleave', function(e) {

  // check for transitioning flag to determine if were transitioning to a child element
  // if not transitioning, we are leaving the container element
  if (transitioning === false) {
    $el.toggleClass('dragging', false);
  }

  e.preventDefault();
  e.stopPropagation();
});

// to allow drop event listener to work
$el.on('dragover', function(e) {
  e.preventDefault();
  e.stopPropagation();
});

$el.on('drop', function(e) {
  alert("drop!");
});

jsfiddle: http://jsfiddle.net/ilovett/U7mJj/

ilovett
la source
0

Vous devez supprimer les événements de pointeur pour tous les objets enfants de la cible de glissement.

function disableChildPointerEvents(targetObj) {
        var cList = parentObj.childNodes
        for (i = 0; i < cList.length; ++i) {
            try{
                cList[i].style.pointerEvents = 'none'
                if (cList[i].hasChildNodes()) disableChildPointerEvents(cList[i])
            } catch (err) {
                //
            }
        }
    }
EddieB
la source