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



Answers

Est-il possible d'empêcher dragleave de tirer lorsque vous faites 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 nœuds 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 #drop pour tout mettre à l'intérieur)

Voici un jsfiddle résolvant l'exemple de http://jsfiddle.net/pimvdb/HU6Mk/1/ .

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

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

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

Question

Le problème que je rencontre est que l'événement dragleave d'un élément est déclenché en dragleave un élément enfant de cet élément. De même, le dragenter n'est pas renvoyé lorsque vous retournez 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 est de notifier l'utilisateur en faisant redistribuer le div lorsque vous faites glisser quelque chose là-bas. Cela fonctionne, mais si vous faites glisser dans l'enfant p , le dragleave est tiré et la div n'est plus rouge. Revenir à la div drop ne le fait pas redevenir rouge. Il est nécessaire de sortir complètement de la div drop et de la faire glisser à nouveau pour la rendre rouge.

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

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




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 @ HD). Voici un jsFiddle que j'ai créé qui démontre cette technique . Malheureusement, cela ne fonctionne pas dans les versions d'Internet Explorer antérieures à IE11, car elles http://caniuse.com/pointer-events prenaient http://caniuse.com/pointer-events .

Heureusement, j'ai été en mesure de trouver une solution de contournement qui fonctionne dans les anciennes versions d'Internet Explorer. Fondamentalement, il implique d'identifier et d'ignorer les événements de dragleave qui se produisent lors du déplacement sur les é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 qui ajoute ou supprime une classe "ignore-drag-leave" de la cible drop. Ensuite, l' dragleave événement dragleave la cible de dragleave 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 démontrant une solution combinée utilisant la détection de fonctionnalités, où les événements de pointeur sont utilisés si pris en charge (actuellement Chrome, Firefox et IE11), et le navigateur revient à ajouter des événements aux nœuds enfants. -dix).




pimvdb ..

Pourquoi ne pas essayer d'utiliser drop au lieu de dragleave . Cela a fonctionné pour moi. hope this solves your problem.

Please check the 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");
                 }
                });



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



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

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

Ensuite, 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




Je ne sais pas si 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 toute la page, mais mon dragleave est déclenché lorsque je glisse sur l'élément enfant. Mon but é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 glissez sur un document, je le montre, et quand vous laissez tomber le parent, il le gère, et quand 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;
});



You need to remove the pointer events for all child objects of the drag target.

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



Just try to use the event.eventPhase. It will set to 2 (Event.AT_TARGET) only if the target ist entered, otherwise it is set to 3 (Event.BUBBLING_PHASE).

I'fe used the eventPhase to bind or unbind the dragleave Event.

$('.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




J'ai écrit un module 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 de tout (téléchargement de fichier, glisser-déposer intégré, déplacement depuis ou vers des sources externes), vous devez vérifier module sorti:

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 de comptoir est ce que j'ai utilisé en goutte-à-goutte, alors la réponse la mieux notée manque des étapes importantes qui vont faire casser les choses 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
    }
});



Une solution très simple consiste à utiliser la propriété CSS pointer-events . Définissez simplement sa valeur à none lors du dragstart sur chaque élément enfant. Ces éléments ne déclencheront plus d'événements liés à la souris, donc ils n'attraperont pas la souris sur eux et ne déclencheront donc pas le dragleave sur le parent.

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




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

J'ai essayé différentes méthodes de vérification pour voir si l'élément e.target est le même que l'élément this , mais n'a pas pu obtenir d'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');
               }
           }



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



J'ai trébuché dans 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 si c'est crossbrowser (peut dépendre même de l'ordre bouillonnant)

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

L'événement bulles à parent de toute façon ainsi 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 à peu près tout. :) cela fonctionne parce que même si onEnter sur l'enfant se déclenche avant onLeave sur le parent, nous le retardons en inversant légèrement l'ordre, ainsi la classe est enlevée d'abord puis récupérée après une miliseconde.




Related