Se activa el dragleave HTML5 al pasar el cursor sobre un elemento secundario


301

El problema que tengo es que el dragleaveevento de un elemento se dispara cuando se pasa el elemento secundario de ese elemento. Además, dragenterno se activa cuando se vuelve a desplazar el elemento primario.

Hice un violín simplificado: 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>

con el siguiente JavaScript:

$('#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");
                 }
                });

Lo que se supone que debe hacer es notificar al usuario haciendo que la gota sea divroja al arrastrar algo allí. Esto funciona, pero si arrastra al pniño, dragleavese dispara y divya no es rojo. Volver a la gota divtampoco lo vuelve rojo. Es necesario salir completamente de la gota divy arrastrarla nuevamente para que quede roja.

¿Es posible evitar dragleavedisparar al arrastrar a un elemento secundario?

Actualización 2017: TL; DR, busque CSS pointer-events: none;como se describe en la respuesta de @ HD a continuación que funciona en navegadores modernos e IE11.


El error que informó pimvdb todavía existe en Webkit a partir de mayo de 2012. Lo he contrarrestado agregando también una clase en dragover, que no está cerca de ser agradable, ya que se dispara con tanta frecuencia, pero parece solucionar el problema un poco.
ajm

2
@ajm: Gracias, eso funciona hasta cierto punto. Sin embargo, en Chrome, hay un destello al entrar o salir del elemento secundario, presumiblemente porque dragleavetodavía se dispara en ese caso.
pimvdb

He abierto un error jQuery UI upvotes son bienvenidos para que puedan decidir poner recursos en ella
fguillen

@fguillen: Lo siento, pero esto no tiene nada que ver con jQuery UI. De hecho, jQuery ni siquiera es necesario para activar el error. Ya presenté un error de WebKit pero no hay ninguna actualización hasta ahora.
pimvdb

2
@pimvdb ¿Por qué no acepta la respuesta de HD?
TylerH

Respuestas:


339

Solo necesita mantener un contador de referencia, incrementarlo cuando obtenga una gragera, disminuir cuando obtenga un dragleave. Cuando el contador está en 0, elimine la clase.

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

Nota: en el caso de caída, restablezca el contador a cero y borre la clase agregada.

Puedes ejecutarlo aquí


8
Dios mío, esta es la solución más obvia y solo tenía UN voto ... Vamos gente, puedes hacerlo mejor. Estaba pensando en eso, pero después de ver el nivel de sofisticación de las primeras respuestas, casi lo descarté. ¿Tuviste algún inconveniente?
Arthur Corenzan

11
Esto no funcionó cuando el borde del elemento que se arrastra toca el borde de otro elemento arrastrable. Por ejemplo, una lista ordenable; arrastrar el elemento hacia abajo, sobre el siguiente elemento arrastrable, no disminuye el contador de nuevo a 0, sino que se atasca en 1. Pero si lo arrastro de lado, fuera de cualquier otro elemento arrastrable, funciona. Fui con pointer-events: nonelos niños. Cuando empiezo a arrastrar, agrego una clase que tiene esta propiedad, cuando se arrastra, elimino la clase. Funcionó muy bien en Safari y Chrome, pero no en Firefox.
Arthur Corenzan

13
Esto funcionó para mí en todos los navegadores, pero Firefox. Tengo una nueva solución que funciona en todas partes en mi caso. En el primero dragenterguardo event.currentTargeten una nueva variable dragEnterTarget. Mientras dragEnterTargetesté configurado, ignoro otros dragentereventos, porque son de niños. En todos los dragleaveeventos lo verifico dragEnterTarget === event.target. Si esto es falso, el evento será ignorado ya que fue disparado por un niño. Si esto es cierto me reinicio dragEnterTargeta undefined.
Pipo

3
Gran solución Sin embargo, necesitaba restablecer el contador a 0 en la función que maneja la aceptación de la caída, de lo contrario, los arrast posteriores no funcionaron como se esperaba.
Liam Johnston

2
@Woody No vi su comentario en la gran lista de comentarios aquí, pero sí, esa es la solución. ¿Por qué no incorporar eso en tu respuesta?
BT

93

¿Es posible evitar que se dispare dragleave cuando se arrastra a un elemento secundario?

Si.

#drop * {pointer-events: none;}

Ese CSS parece ser suficiente para Chrome.

Al usarlo con Firefox, el #drop no debería tener nodos de texto directamente (de lo contrario, hay un problema extraño en el que un elemento "lo deja solo" ), por lo que sugiero dejarlo con un solo elemento (por ejemplo, use un div dentro #drop para poner todo dentro)

Aquí hay un jsfiddle para resolver el ejemplo de la pregunta original (rota) .

También hice una versión simplificada bifurcada del ejemplo @Theodore Brown, pero basada solo en este CSS.

Sin embargo, no todos los navegadores tienen implementado este CSS: http://caniuse.com/pointer-events

Al ver el código fuente de Facebook, pude encontrar esto pointer-events: none;varias veces, sin embargo, probablemente se use junto con fallas de degradación elegantes. Al menos es tan simple y resuelve el problema para muchos entornos.


La propiedad puntero-eventos es la solución correcta en el futuro, pero desafortunadamente no funciona en IE8-IE10, que todavía se usan ampliamente. Además, debo señalar que su jsFiddle actual ni siquiera funciona en IE11, ya que no agrega los oyentes de eventos necesarios y la prevención de comportamiento predeterminada.
Theodore Brown

2
El uso de eventos de puntero es una buena respuesta, luché un poco antes de descubrir por mí mismo, esa respuesta debería ser mayor.
floribon

Fantástica opción para aquellos de nosotros que tenemos la suerte de no tener que admitir navegadores antiguos.
Luke Hansford

77
¿Qué pasa si sus hijos son un botón? oO
David

99
funciona solo si no tiene otros elementos de controlador (editar, eliminar) dentro del área, porque esta solución también los bloquea ..
Zoltán Süle

45

Aquí, la solución más simple de Cross-Browser (en serio):

jsfiddle <- intenta arrastrar algún archivo dentro del cuadro

Puedes hacer algo así:

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 pocas palabras, crea una "máscara" dentro de la zona de caída, con ancho y alto heredados, posición absoluta, que solo se mostrará cuando comience el dragover.
Entonces, después de mostrar esa máscara, puedes hacer el truco adjuntando los otros eventos dragleave & drop en ella.

Después de irse o dejarse caer, simplemente oculta la máscara nuevamente.
Simple y sin complicaciones.

(Obs .: consejos de Greg Pettit: debe asegurarse de que la máscara se desplace por toda la caja, incluido el borde)


No estoy seguro de por qué, pero no funciona de manera consistente con Chrome. A veces, salir del área mantiene la máscara visible.
Greg Pettit

2
En realidad, es la frontera. Haga que la máscara se superponga al borde, sin un borde propio, y debería funcionar bien.
Greg Pettit

1
Tenga en cuenta que su jsfiddle tiene un error, en drag_drop, debe eliminar la clase de desplazamiento en "#box" no "# box-a"
entropía

Esta es una buena solución, pero de alguna manera no funcionó para mí. Finalmente descubrí una solución alternativa. Para aquellos de ustedes que están buscando algo más. puedes probar esto: github.com/bingjie2680/jquery-draghover
bingjie2680

No. No quiero perder el control sobre los elementos secundarios. He creado un simple complemento jQuery que se ocupa del problema que hace el trabajo por nosotros. Mira mi respuesta .
Luca Fagioli

39

Ha pasado bastante tiempo después de que se haga esta pregunta y se proporcionan muchas soluciones (incluidos los hacks feos).

Logré solucionar el mismo problema que tuve recientemente gracias a la respuesta en esta respuesta y pensé que podría ser útil para alguien que llega a esta página. La idea es almacenar el evenet.targeten ondrageentercada se le llama en cualquiera de los elementos principales o secundarios. Luego, ondragleaveverifique si el objetivo actual ( event.target) es igual al objeto que almacenó ondragenter.

El único caso en que estos dos coinciden es cuando su arrastre sale de la ventana del navegador.

La razón por la que esto funciona bien es cuando el mouse deja un elemento (digamos el1) y entra en otro elemento (digamos el2), primero el2.ondragenterse llama y luego el1.ondragleave. Solo cuando el arrastre salga / ingrese a la ventana del navegador, event.targetestará ''en ambos el2.ondragenter y el1.ondragleave.

Aquí está mi muestra de trabajo. Lo he probado en IE9 +, Chrome, Firefox y 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;
    };
})();

Y aquí hay una página html simple:

<!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>

Con un estilo adecuado, lo que he hecho es hacer que el div interno (# file-drop-area) sea mucho más grande cada vez que se arrastra un archivo a la pantalla para que el usuario pueda soltar fácilmente los archivos en el lugar adecuado.


44
Esta es la mejor solución, es mejor que el contador (especialmente si delega eventos) y también funciona con elementos secundarios arrastrables.
Fred

3
Esto no ayuda con el problema de los eventos duplicados de dragenter
BT

¿Funciona esto para "niños" que son pseudo-elementos o pseudo-clases css? No pude hacerlo, pero tal vez lo estaba haciendo mal.
Josh Bleecher Snyder

1
A partir de marzo de 2020, también tuve la mejor suerte con esta solución. Nota: algunos linters pueden quejarse del uso de ==over ===. Está comparando las referencias de objeto de destino del evento, por lo que también ===funciona bien.
Alex

33

La forma "correcta" de resolver este problema es deshabilitar los eventos de puntero en elementos secundarios del destino de colocación (como en la respuesta de @ HD). Aquí hay un jsFiddle que creé que demuestra esta técnica . Desafortunadamente, esto no funciona en versiones de Internet Explorer anteriores a IE11, ya que no admitían eventos de puntero .

Por suerte, yo era capaz de llegar a una solución, que hace el trabajo en las versiones antiguas de IE. Básicamente, implica identificar e ignorar dragleaveeventos que ocurren al arrastrar elementos secundarios. Debido a que el dragenterevento se dispara en los nodos secundarios antes del dragleaveevento en el primario, se pueden agregar escuchas de eventos separados a cada nodo secundario que agregan o eliminan una clase "ignorar-arrastrar-dejar" del destino de colocación. Entonces, el dragleaveoyente de eventos del destino puede simplemente ignorar las llamadas que ocurren cuando esta clase existe. Aquí hay un jsFiddle que demuestra esta solución . Está probado y funciona en Chrome, Firefox e IE8 +.

Actualizar:

He creado un jsFiddle demostrando una solución combinada mediante la detección de características, donde se utilizan los eventos de puntero si es compatible (actualmente Chrome, Firefox y IE11), y el navegador vuelve a caer a añadir eventos a los nodos secundarios si el soporte evento puntero no está disponible (IE8 -10).


Esta respuesta muestra una solución alternativa para el disparo indeseable, pero descuida la pregunta "¿Es posible evitar que Dragleave se dispare al arrastrar a un elemento secundario?" enteramente.
HD

El comportamiento puede volverse extraño al arrastrar un archivo desde fuera del navegador. En Firefox, tengo "Ingresando niño -> Ingresando padre -> Saliendo niño -> Ingresando niño -> Saliendo niño" sin salir del padre, que se fue con la clase "sobre". IE antiguo necesitaría un reemplazo attachEvent para addEventListener.
HD

Esa solución depende en gran medida del burbujeo, lo "falso" en todo addEventListener debe enfatizarse como esencial (aunque ese es el comportamiento predeterminado), ya que muchas personas pueden no saberlo.
HD

Ese solo arrastrable agrega un efecto a la zona de caída que no aparece cuando la zona de caída se usa para otros objetos arrastrables que no activan el evento dragstart. Tal vez usar todo como una zona de caída para el efecto de arrastrar mientras se mantiene la zona de caída real con otros controladores haría eso.
HD

1
@HD Actualicé mi respuesta con información sobre el uso de la propiedad CSS puntero-eventos para evitar que el dragleaveevento se active en Chrome, Firefox e IE11 +. También actualicé mi otra solución para admitir IE8 e IE9, además de IE10. Es intencional que el efecto dropzone solo se agregue al arrastrar el enlace "Arrastrarme". Otros pueden sentirse libres de cambiar este comportamiento según sea necesario para respaldar sus casos de uso.
Theodore Brown

14

El problema es que el dragleaveevento se desencadena cuando el mouse se coloca delante del elemento secundario.

He intentado varios métodos de verificación para ver si el e.targetelemento es el mismo que el thiselemento, pero no pude obtener ninguna mejora.

La forma en que solucioné este problema fue un poco pirata, pero funciona al 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');
               }
           }

1
¡Gracias! Sin embargo, no puedo hacer que esto funcione en Chrome. ¿Podrías proporcionar un violín funcional de tu truco?
pimvdb

3
Estaba pensando en hacerlo comprobando también las coordenadas. Hiciste la mayor parte del trabajo por mí, gracias :). Sin embargo, tuve que hacer algunos ajustes:if (e.x >= (rect.left + rect.width) || e.x <= rect.left || e.y >= (rect.top + rect.height) || e.y <= rect.top)
Christof

1
No funcionará en Chrome porque su evento no tiene e.xy e.y.
Hengjie

Me gusta esta solución No funcionó en Firefox primero. Pero si reemplaza ex con e.clientX y ey con e.clientY, funciona. También funciona en Chrome.
Nicole Stutz el

No funcionó en Chrome para mí, ni lo que sugirieron Chris o Daniel Stuts
Timo Huovinen

14

Si está utilizando HTML5, puede obtener el cliente del padre.

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

Luego, en parent.dragleave ():

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

aquí hay un jsfiddle


2
Excelente respuesta
broc.seib

Gracias amigo, me gusta el enfoque simple de JavaScript.
Tim Gerhard el

Cuando se usa en elementos que tienen border-radius, mover el puntero cerca de la esquina en realidad dejaría el elemento, pero este código aún pensaría que estamos dentro (hemos dejado el elemento pero todavía estamos en el rectángulo delimitador). Entonces el dragleavecontrolador de eventos no se llamará en absoluto.
Jay Dadhania

11

Una solución muy simple es usar la pointer-eventspropiedad CSS . Simplemente establezca su valor ennone en dragstart en cada elemento secundario. Estos elementos ya no activarán eventos relacionados con el mouse, por lo que no atraparán el mouse sobre ellos y, por lo tanto, no activarán el dragleave en el padre.

No olvide volver a establecer esta propiedad autoal finalizar el arrastre;)


11

Esta solución bastante simple me está funcionando hasta ahora, suponiendo que su evento se adjunte a cada elemento de arrastre individualmente.

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

¡Esto es genial! gracias por compartir @kenneth, me salvaste el día! Muchas de las otras respuestas solo se aplican si tiene una sola zona desplegable.
Antoni

Para mí, funciona en Chrome y Firefox, pero la identificación no funciona en Edge. Ver: jsfiddle.net/iwiss/t9pv24jo
iwis

8

Solución muy simple:

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

Esto no parece funcionar en Safari 12.1: jsfiddle.net/6d0qc87m
Adam Taylor el

7

Puedes arreglarlo en Firefox con un poco de inspiración del código fuente de 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');
        }
    }

}

Desafortunadamente, no funciona en Chrome porque relatedTargetparece no existir en los dragleaveeventos, y supongo que está trabajando en Chrome porque su ejemplo no funcionó en Firefox. Aquí hay una versión con el código anterior implementado.


Muchas gracias, pero de hecho es Chrome estoy tratando de resolver este problema de.
pimvdb

55
@pimvdb Veo que has registrado un error , solo dejaré una referencia aquí en caso de que alguien más encuentre esta respuesta.
robertc

De hecho lo hice, pero olvidé agregar un enlace aquí. Gracias por hacer eso
pimvdb

El error de Chrome se ha solucionado mientras tanto.
phk

7

Y aquí va, una solución para 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!');
                    }
                })

¿Entra en «if (typeof event.clientX === 'undefined')»?
Aldekein

1
Funcionó bien, pero podría haber otra ventana sobre el navegador, por lo que obtener la ubicación del mouse y compararlo con el área de pantalla rectangular no es suficiente.
HD

Estoy de acuerdo con @HD Además, esto causará problemas cuando el elemento sea grande, border-radiuscomo expliqué en mi comentario sobre la respuesta de @ azlar anterior.
Jay Dadhania

7

Aquí hay otra solución usando 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');
   }
}

Espero que esto funcione, aquí hay un violín .


3
Esto no funcionó para mí fuera de la caja. Necesitaba usar event.clientX, event.clientYen su lugar, ya que son relativos a la ventana gráfica y no a la página. Espero que eso ayude a otra alma perdida por ahí.
Jon Abrams

7

Una solución simple es agregar la regla css pointer-events: noneal componente hijo para evitar el desencadenante de ondragleave. Ver ejemplo:

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>


Tuve este mismo problema y su solución funciona muy bien. Consejo para otros, este fue mi caso: si el elemento hijo que está tratando de ignorar el evento de arrastre es el clon del elemento que se está arrastrando (tratando de lograr una vista previa visual), puede usar esto dentro de dragstart al crear el clon :dragged = event.target;clone = dragged.cloneNode();clone.style.pointerEvents = 'none';
Scaramouche

4

No estoy seguro de si este navegador cruzado, pero lo probé en Chrome y resuelve mi problema:

Quiero arrastrar y soltar un archivo en toda la página, pero mi dragleave se activa cuando arrastro sobre el elemento secundario. Mi solución fue mirar la x e y del mouse:

Tengo un div que se superpone a toda mi página, cuando la página se carga lo oculto.

cuando arrastra sobre el documento, lo muestro, y cuando suelta sobre el padre, lo maneja, y cuando sale del padre, verifico x e 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;
});

1
Hacky pero inteligente. Me gusta. Trabajó para mi.
cupcakekid

1
¡Oh, cómo desearía que esta respuesta tuviera más votos! Gracias
Kirby

4

He escrito una pequeña biblioteca llamada Dragster para manejar este problema exacto, funciona en todas partes excepto silenciosamente sin hacer nada en IE (que no admite DOM Event Constructors, pero sería bastante fácil escribir algo similar usando los eventos personalizados de jQuery)


Muy útil (al menos para mí donde solo me importa Chrome).
M Katz

4

Estaba teniendo el mismo problema e intenté usar la solución pk7s. Funcionó, pero podría hacerse un poco mejor sin ningún elemento dom adicional.

Básicamente, la idea es la misma: agregue una superposición no visible adicional sobre el área desplegable. Solo hagamos esto sin ningún elemento dom adicional. Aquí está la parte donde los pseudo-elementos CSS vienen a jugar.

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

Esta regla posterior creará una superposición completamente cubierta para el área desplegable.

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

Aquí está la solución completa: http://jsfiddle.net/F6GDq/8/

Espero que ayude a cualquiera con el mismo problema.


1
Ocasionalmente no funciona en cromo (no atrapa el dragleave correctamente)
Timo Huovinen

4

Simplemente verifique si el elemento arrastrado es un elemento secundario, si lo es, no elimine su clase de estilo 'dragover'. Bastante simple y funciona para mí:

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

Parece la solución más simple para mí y resolvió mi problema. ¡Gracias!
Francesco Marchetti-Stasi

3

Me he encontrado con el mismo problema y aquí está mi solución, que creo que es mucho más fácil que la anterior. No estoy seguro si es crossbrowser (puede depender incluso del orden de burbujeo)

Usaré jQuery por simplicidad, pero la solución debe ser independiente del marco.

El evento burbujea a los padres de cualquier manera, dado:

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

Adjuntamos eventos

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

Y eso es todo. :) funciona porque aunque onEnter en los incendios secundarios antes que onLeave en los padres, lo retrasamos un poco invirtiendo el orden, por lo que la clase se elimina primero y luego se vuelve a aplicar después de un milisegundo.


Lo único que hace este fragmento es evitar que se elimine la clase 'suspendida' volviendo a aplicarla en el siguiente ciclo de marca (haciendo que el evento 'dragleave' sea inútil).
nulo

No es inútil Si deja a los padres, funcionará como se esperaba. El poder de esta solución es su simplicidad, no es lo ideal o lo mejor que existe. La mejor solución sería marcar una entrada en un niño, en onleave verifique si acabamos de ingresar un niño y, de ser así, no desencadenar un evento de ausencia. Sin embargo, necesitará pruebas, guardias adicionales, animar a los nietos, etc.
Marcin Raczkowski

3

Escribí un módulo de arrastrar y soltar llamado drip-drop que corrige este comportamiento extraño, entre otros. Si está buscando un buen módulo de arrastrar y soltar de bajo nivel que pueda usar como base para cualquier cosa (carga de archivos, arrastrar y soltar en la aplicación, arrastrar desde o hacia fuentes externas), debe verificar esto módulo fuera:

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

Así es como haría lo que intenta hacer en goteo:

$('#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')
    }
  })
})

Para hacer esto sin una biblioteca, la técnica de contador es la que utilicé en el goteo, aunque la respuesta mejor calificada omite pasos importantes que harán que las cosas se rompan para todo excepto la primera caída. Aquí se explica cómo hacerlo correctamente:

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

Una solución de trabajo alternativa, un poco más 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) {

Esto no parece funcionar siempre en Chrome. En ocasiones recibiría clientXmás de 0 cuando el mouse está fuera de la caja. De acuerdo, mis elementos sonposition:absolute
Hengjie

¿Sucede todo el tiempo o solo a veces? Porque si el mouse se mueve demasiado rápido (por ejemplo, fuera de la ventana), puede obtener valores incorrectos.
Profet

Sucede el 90% del tiempo. Hay casos raros (1 de cada 10 veces) en los que puedo hacer que llegue a 0. Intentaré nuevamente mover el mouse más lento, pero no podría decir que me estaba moviendo rápidamente (quizás lo que llamarías velocidad normal).
Hengjie

2

Después de pasar tantas horas, recibí esa sugerencia funcionando exactamente como estaba previsto. Quería proporcionar una señal solo cuando los archivos se arrastraban, y el arrastre de documentos, dragleave causaba parpadeos dolorosos en el navegador Chrome.

Así es como lo resolví, también arrojando pistas adecuadas para el usuario.

$(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
});

1

Tuve un problema similar: mi código para ocultar la zona de caída en el evento dragleave para el cuerpo se activó de forma contagiosa al pasar el cursor sobre elementos secundarios que hacen que la zona de caída parpadee en Google Chrome.

Pude resolver esto programando la función para ocultar la zona de caída en lugar de llamarla de inmediato. Luego, si se dispara otro dragover o dragleave, se cancela la llamada de función programada.

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

Esto es lo que terminé haciendo también, pero aún es incompleto.
mpen

1

El evento " dragleave " se activa cuando el puntero del mouse sale del área de arrastre del contenedor de destino.

Lo cual tiene mucho sentido ya que en muchos casos solo el padre puede ser descartable y no los descendientes. Creo que event.stopPropogation () debería haber manejado este caso, pero parece que no funciona.

Anteriormente, algunas soluciones parecen funcionar para la mayoría de los casos, pero falla en el caso de aquellos niños que no admiten eventos de arrastre / dragleave , como iframe .

1 solución es verificar el event.relatedTarget y verificar si reside dentro del contenedor, luego ignore el evento dragleave como lo he hecho aquí:

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

Puedes encontrar un violín que funcione aquí !


1

Resuelto ..!

Declare cualquier matriz para ex:

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

No hay que preocuparse por los elementos secundarios.


1

Luché MUCHO con esto, incluso después de leer todas estas respuestas, y pensé que podría compartir mi solución con ustedes, porque pensé que podría ser uno de los enfoques más simples, aunque algo diferente. Mi pensamiento era simplemente omitir eldragleave completo el detector de eventos y codificar el comportamiento de dragleave con cada nuevo evento dragenter activado, mientras me aseguraba de que los eventos dragenter no se activaran innecesariamente.

En mi ejemplo a continuación, tengo una tabla, donde quiero poder intercambiar el contenido de las filas de la tabla entre sí a través de la API de arrastrar y soltar. Activado dragenter, se agregará una clase CSS al elemento de fila al que está arrastrando actualmente su elemento, para resaltarlo, y activado , dragleavese eliminará esta clase.

Ejemplo:

Tabla HTML muy básica:

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

Y la función de controlador de eventos dragenter, añadido en cada celda de la tabla (a un lado dragstart, dragover, drop, y dragendlos manipuladores, los cuales no son específicos de esta cuestión, por lo que no copiados aquí):

/*##############################################################################
##                              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;
  }
}

Se dejaron comentarios muy básicos en el código, de modo que este código también es adecuado para principiantes. ¡Espero que esto te ayude! Tenga en cuenta que, por supuesto, deberá agregar todos los oyentes de eventos que mencioné anteriormente en cada celda de la tabla para que esto funcione.


0

Aquí hay otro enfoque basado en el momento de los eventos.

El dragenterelemento principal puede capturar el evento enviado desde el elemento secundario y siempre se produce antes del dragleave. El tiempo entre estos dos eventos es realmente corto, más corto que cualquier acción posible del ratón humano. Entonces, la idea es memorizar el momento en que dragentersucede y filtrardragleave eventos que ocurren "no demasiado rápido" después de ...

Este breve ejemplo funciona en Chrome y 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

pimvdb ..

¿Por qué no pruebas usando drop en lugar de dragleave ? Funcionó para mi. Espero que esto resuelva tu problema.

Por favor revise el 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");
                 }
                });

2
Si el usuario cambia de opinión y arrastra el archivo fuera del navegador, esto permanecerá en rojo.
ZachB

0

Puede usar un tiempo de espera con una transitioningbandera y escuchar en el elemento superior. dragenter/ dragleavede los eventos secundarios aparecerán en el contenedor.

Dado que dragenteren el elemento hijo se dispara antes dragleavedel contenedor, configuraremos el show de bandera como en transición durante 1 ms ... el dragleaveoyente verificará la bandera antes de que el 1ms esté activo.

El indicador será verdadero solo durante las transiciones a elementos secundarios, y no será verdadero al pasar a un elemento primario (del contenedor)

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

Debe eliminar los eventos de puntero para todos los objetos secundarios del objetivo de arrastre.

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) {
                //
            }
        }
    }
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.