web-dev-qa-db-fra.com

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

Le problème que je rencontre est que l'événement dragleave d'un élément est déclenché lors du survol d'un élément enfant de cet élément. De même, dragenter n'est pas déclenché lors du survol de nouveau 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’elle est supposée faire, c’est notifier l’utilisateur en faisant glisser la variable div en rouge lorsque vous y faites glisser quelque chose. Cela fonctionne, mais si vous glissez dans l'enfant p, la dragleave est déclenchée et la div n'est plus rouge. Revenir à la liste déroulante div ne le rend également pas rouge. Il est nécessaire de sortir complètement de la liste déroulante div et de la glisser à nouveau pour la rendre rouge.

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

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

238
pimvdb

Vous avez juste besoin de garder un compteur de références, de l’incrémenter quand vous avez un dragenter, de le décrémenter quand vous avez 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, réinitialisez le compteur à zéro et effacez la classe ajoutée.

Vous pouvez le lancer ici

278
Woody

Est-il possible d'empêcher le glisser-glisser de tirer lorsque vous faites glisser dans un élément enfant?

Oui.

#drop * {pointer-events: none;}

Ce CSS semble être suffisant pour Chrome.

Lorsqu’on l’utilise avec Firefox, le #drop ne devrait pas avoir de nœuds de texte directement (sinon, il existe un problème étrange dans lequel un élément "le laisse à lui-même" ); utilisez un div inside #drop pour tout mettre à l'intérieur)

Voici un jsfiddle résolvant la question originale (cassé) exemple .

J'ai également créé une version simplifiée forkée à partir de l'exemple de @Theodore Brown, mais basée uniquement sur ce CSS.

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

En voyant le code source de Facebook, je pouvais trouver ce pointer-events: none; à plusieurs reprises, mais il est probablement utilisé conjointement avec des solutions de secours en cas de dégradation. Au moins, c'est si simple et cela résout le problème dans de nombreux environnements.

62
H.D.

Ici, la solution multi-navigateur 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 largage, avec la largeur et la hauteur héritées, la position absolue, qui s'affichera au démarrage du glisser-déposer.
Donc, après avoir montré ce masque, vous pouvez résoudre le problème en attachant les autres événements glisser-déplacer et déposer dessus.

Après avoir quitté ou laissé tomber, vous masquez à nouveau le masque. 
Simple et sans complications.

(Obs.: Conseil de Greg Pettit - Vous devez vous assurer que le masque plane au-dessus de la boîte, y compris la bordure)

38

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 @ H.D.). 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, j'ai réussi à trouver une solution de contournement qui fonctionne dans les anciennes versions d'IE. En gros, il s’agit d’identifier et d’ignorer les événements dragleave qui surviennent lors d’un glissement sur des éléments enfants. Étant donné que l'événement dragenter est déclenché sur les nœuds enfants avant l'événement dragleave sur le parent, des écouteurs d'événement distincts peuvent être ajoutés à chaque nœud enfant, ce qui permet d'ajouter ou de supprimer une classe "ignorer-glisser-laisser" de la bibliothèque. déposer la cible. Ensuite, l'écouteur d'événements dragleave de la cible de dépôt peut simplement ignorer les appels qui surviennent lorsque cette classe existe. Voici un jsFiddle montrant cette solution de contournement . Il est testé et fonctionne sous Chrome, Firefox et IE8 +.

Mise à jour:

J'ai créé n jsFiddle illustrant une solution combinée à l'aide de 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 l'événement de pointeur le support n'est pas disponible (IE8-10).

34
Theodore Brown

Cela fait un certain temps que cette question est posée et que de nombreuses solutions (y compris de très mauvais programmes) sont proposées. 

J'ai réussi à résoudre le même problème que j'ai eu récemment grâce à la réponse dans ce answer et j'ai pensé que cela pourrait être utile à quelqu'un qui passe par cette page . evenet.target dans ondrageenter chaque fois qu'il est appelé sur l'un des éléments parent ou enfant. Puis, dans ondragleave, vérifiez si la cible actuelle (event.target) est égale à l'objet que vous avez stocké dans ondragenter.

Le seul cas où ces deux éléments correspondent est lorsque votre glisser-déposer 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 un autre élément (par exemple, el2), le el1.ondragenter est d'abord appelé, puis el2.ondragleave. event.target sera '' à la fois dans el1.ondragenter et el2.ondragleave uniquement lorsque la glissade quitte/entre dans la fenêtre du navigateur.

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 approprié, ce que j'ai fait est de rendre la division interne (# zone de dépôt de fichier) beaucoup plus grande chaque fois qu'un fichier est déplacé vers l'écran de sorte que l'utilisateur puisse facilement déposer les fichiers au bon endroit.

25
Ali Motevallian

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

J'ai essayé diverses méthodes pour vérifier si l'élément e.target est identique à l'élément this, mais je n'ai pu obtenir aucune amélioration.

La façon dont j'ai résolu ce problème était un peu un bidouillage, 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');
               }
           }
10
Greg

Une solution très simple consiste à utiliser la propriété CSS pointer-events . Il suffit de définir sa valeur sur none lors de dragstart sur chaque élément enfant. Ces éléments ne déclencheront plus d'événements liés à la souris, ils ne la verront donc pas et ne déclenchera donc pas le dragleave sur le parent. 

N'oubliez pas de redéfinir cette propriété sur auto lorsque vous terminez le glissement;)

8
FruityFred

Vous pouvez le réparer dans Firefox en vous inspirant 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 relatedTarget ne semble pas exister lors des événements dragleave et je suppose que vous travaillez dans Chrome car votre exemple n'a pas fonctionné dans Firefox. Voici une version avec le code ci-dessus implémenté.

8
robertc

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!');
                    }
                })
7
Aldekein

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');
   }
}

Espérons que cela fonctionne, voici un violon .

6
marcelj

J'ai écrit une petite bibliothèque appelée Dragster pour gérer ce problème, fonctionne partout sauf 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)

5
Ben

Pas sûr que ce navigateur croisé, mais j'ai testé dans Chrome et cela résout mon problème:

Je veux glisser et déposer un fichier sur la page entière, mais mon glisser-glisser est déclenché lorsque je glisse sur l'élément enfant. Ma solution était de regarder le x et le y de la souris:

j'ai un div qui recouvre toute ma page, quand la page se charge je le 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;
});
4
chrisallick

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

En gros, l’idée est la même: ajouter une couche supplémentaire invisible sur la surface de dépôt. Permet seulement 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 suivante créera une superposition entièrement couverte pour la zone de dépôt.

#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 quelqu'un avec le même problème.

4
rasmusx

Une solution simple consiste à ajouter la règle css pointer-events: none au composant enfant pour empêcher le déclenchement de ondragleave. Voir 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>

4
Alexandre Annic

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 crossbrowser (peut dépendre de l'ordre même du bouillonnement)

J'utiliserai jQuery pour plus de simplicité, mais la solution doit être indépendante du framework.

L'événement crée une bulle parentale dans les deux sens, de la manière suivante:

<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 à peu près tout. :) cela fonctionne car même si onEnter sur l'enfant se déclenche avant onLeave sur le parent, nous le retardons légèrement en inversant l'ordre, de sorte que la classe est supprimée en premier, puis renvoyée au bout d'une seconde.

3
Marcin Raczkowski

Il suffit de vérifier si l'élément survolé est un enfant, s'il ne l'est pas, alors ne supprimez pas votre classe de style 'dragover'. Assez simple et fonctionne pour moi: 

 $yourElement.on('dragleave dragend drop', function(e) {
      if(!$yourElement.has(e.target).length){
           $yourElement.removeClass('is-dragover');
      }
  })
3
sofarsoghood

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

3
azlar

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) {
2
Profet

Après avoir passé tant d’heures, cette suggestion a fonctionné exactement comme prévu. Je souhaitais fournir un repère uniquement lorsque les fichiers étaient déplacés et que le glissement de document entraînait des scintillements douloureux sur le navigateur Chrome.

C’est ainsi que j’ai résolu le problème, en jetant également des informations appropriées 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
});
2
visitsb

J'ai écrit un module de glisser-déposer appelé drip-drop qui corrige ce comportement bizarre, entre autres. Si vous recherchez un bon module de glisser-déposer de bas niveau que vous pouvez utiliser comme base pour tout (téléchargement de fichier, glisser-déposer intégré à l'application, glisser de ou vers des sources externes), cochez cette case. module sur:

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 faire cela sans bibliothèque, la technique du compteur est celle que j’ai utilisée dans le goutte-à-goutte, mais la réponse la mieux notée manque les étapes importantes qui provoqueront des ruptures pour tout sauf la première goutte. Voici comment le faire 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
    }
});
2
B T

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

Cela a beaucoup de sens car dans de nombreux cas, seul le parent peut être droppable et non les descendants . Je pense que event.stopPropogation () aurait dû gérer ce cas, mais semble ne pas le faire t faire le tour.

Certaines des solutions susmentionnées semblent fonctionner dans la plupart des cas, mais échouent dans le cas d'enfants ne prenant pas en charge les événements dragenter/dragleave, tels que iframe

Une solution consiste à vérifier le event.relatedTarget et à vérifier s'il réside dans le 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 !

1
Lalit Nankani

J'ai eu un problème similaire: mon code pour masquer l'événement Droplone sur dragleave pour body a été déclenché de façon concomitante lorsque le survol d'éléments enfant faisait scintiller la zone de largage dans Google Chrome.

J'ai pu résoudre ce problème en programmant la fonction permettant de masquer Dropzone au lieu de l'appeler immédiatement. Ensuite, si un autre glisser-déposer ou dragleave 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);
1
Arseny

Vous pouvez utiliser une temporisation avec un indicateur transitioning et écouter sur l'élément du haut. dragenter/dragleave à partir d'événements enfants se propagera dans le conteneur.

Puisque dragenter sur l'élément enfant est déclenché avant dragleave du conteneur, nous définirons l'indicateur comme étant en transition pendant 1 ms ... l'écouteur dragleave recherchera l'indicateur avant que le signal 1ms soit activé.

L'indicateur sera vrai uniquement lors des transitions vers les éléments enfants et ne le sera pas 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/

0
ilovett

utilisez ce code http://jsfiddle.net/HU6Mk/258/ :

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

         dragleave: function(event) {
             var x = event.clientX, y = event.clientY,
                 elementMouseIsOver = document.elementFromPoint(x, y);
             if(!$(elementMouseIsOver).closest('.red').length) {
                 $(this).removeClass('red');
             }
        }
    });
0
Mohammad Rezaei

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

L'événement dragenter 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 très court, plus court que toute action humaine possible de la souris. L’idée est donc de mémoriser le moment où une dragenter se produit et de filtrer les événements dragleave 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
    }
})
0
smrtl

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

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) {
                //
            }
        }
    }
0
EddieB

Essayez simplement d'utiliser event.eventPhase. Il sera défini sur 2 (Event.AT_TARGET) uniquement si la cible est entrée. Sinon, la valeur est 3 (Event.BUBBLING_PHASE).

J'ai utilisé eventPhase pour lier ou délier l'événement Dragleave.

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

  if(e.eventPhase === Event.AT_TARGET) {

    $('.dropzone').addClass('drag-over');

    $('.dropzone').on('dragleave', function(e) {
      $('.dropzone').removeClass('drag-over');
    });

  }else{

    $('.dropzone').off('dragleave');

  }
})

Guido

0
GNaruhn

J'ai trouvé une solution similaire mais plus élégante à la réponse de @ azlar, et voici ma solution:

$(document).on({
    dragenter: function(e) {
        e.stopPropagation();
        e.preventDefault();
        $("#dragging").show();
    },
    dragover: function(e) {
        e.stopPropagation();
        e.preventDefault();
    },
    dragleave: function(e) {
        e.stopPropagation();
        e.preventDefault();
        if (e.clientX <= 0 ||
            // compare clientX with the width of browser viewport
            e.clientX >= $(window).width() ||
            e.clientY <= 0 ||
            e.clientY >= $(window).height())
            $("#dragging").hide();
    }
});

Cette méthode détecte si la souris a quitté la page .Elle fonctionne bien dans Chrome et Edge.

0
zysite

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

if (evt.currentTarget.contains(evt.relatedTarget)) {
  return;
}
0
Kenneth Spencer

Solution très simple:

parent.addEventListener('dragleave', function(evt) {
    if (!parent.contains(evt.relatedTarget)) {
        // Here it is only dragleave on the parent
    }
}
0
Owen M

pimvdb ..

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

S'il vous plaît 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");
                 }
                });
0
abhi