Dragleave HTML5 attivato durante il passaggio del mouse su un elemento figlio


301

Il problema che sto riscontrando è che l' dragleaveevento di un elemento viene generato quando si passa con il mouse su un elemento figlio di quell'elemento. Inoltre, dragenternon viene attivato quando si sposta di nuovo indietro l'elemento genitore.

Ho realizzato un violino semplificato: 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 il seguente 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");
                 }
                });

Quello che dovrebbe fare è avvisare l'utente rendendo divrosso il drop quando si trascina qualcosa lì. Funziona, ma se trascini nel pbambino, dragleaveviene sparato e divnon è più rosso. Tornare indietro al drop divnon lo rende di nuovo rosso. È necessario uscire completamente dalla goccia dive trascinarla di nuovo per renderla rossa.

È possibile impedire lo dragleavesparo quando si trascina in un elemento figlio?

Aggiornamento 2017: TL; DR, cerca CSS pointer-events: none;come descritto nella risposta di @ HD di seguito che funziona nei browser moderni e IE11.


Il bug segnalato da pimvdb esiste ancora in Webkit a partire da maggio 2012. L'ho contrastato aggiungendo anche una classe in dragover, che non è per nulla simpatica poiché si attiva così spesso, ma sembra correggere un po 'il problema.
Ajm

2
@ajm: grazie, funziona fino a un certo punto. Tuttavia, su Chrome, c'è un lampo quando si entra o si esce dall'elemento figlio, presumibilmente perché dragleaveè ancora attivato in quel caso.
pimvdb,

Ho aperto un upgrade di bug dell'interfaccia utente di jQuery in modo che possano decidere di mettere le risorse su di esso
fguillen,

@fguillen: mi dispiace ma questo non ha nulla a che fare con l'interfaccia utente di jQuery. In effetti, jQuery non è nemmeno necessario per attivare il bug. Ho già presentato un bug di WebKit ma al momento non ci sono aggiornamenti.
pimvdb,

2
@pimvdb Perché non accetti la risposta di HD?
TylerH,

Risposte:


339

Devi solo mantenere un contatore di riferimento, incrementarlo quando ottieni un dragenter, decrementare quando ottieni un dragleave. Quando il contatore è a 0, rimuovere 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');
        }
    }
});

Nota: nell'evento drop, reimpostare il contatore su zero e cancellare la classe aggiunta.

Puoi eseguirlo qui


8
OMG questa è la soluzione più ovvia e ha avuto UN SOLO voto ... Forza gente, puoi fare di meglio. Ci stavo pensando ma dopo aver visto il livello di sofisticazione delle prime poche risposte l'ho quasi scartato. Hai avuto degli svantaggi?
Arthur Corenzan,

11
Questo non ha funzionato quando il bordo dell'elemento trascinato tocca il bordo di un altro elemento trascinabile. Ad esempio un elenco ordinabile; trascinando l'elemento verso il basso, sopra l'elemento trascinabile successivo non diminuisce il contatore di nuovo a 0, ma rimane bloccato su 1. Ma se lo trascino lateralmente, fuori da qualsiasi altro elemento trascinabile, funziona. Sono andato con pointer-events: nonei bambini. Quando comincio a trascinare, aggiungo una classe che ha questa proprietà, quando trascino il mouse rimuovo la classe. Ha funzionato bene su Safari e Chrome, ma non su Firefox.
Arthur Corenzan,

13
Questo ha funzionato per me in tutti i browser, tranne Firefox. Ho una nuova soluzione che funziona ovunque nel mio caso. Sul primo, dragentersalvo event.currentTargetuna nuova variabile dragEnterTarget. Finché dragEnterTargetè impostato, ignoro ulteriori dragentereventi, perché provengono da bambini. In ogni dragleavecaso controllo dragEnterTarget === event.target. Se questo è falso, l'evento verrà ignorato poiché è stato generato da un bambino. Se questo è vero ho resettato dragEnterTargeta undefined.
Pipo,

3
Ottima soluzione Avevo bisogno di reimpostare il contatore su 0 nella funzione che gestisce l'accettazione del drop, altrimenti i trascini successivi non funzionavano come previsto.
Liam Johnston,

2
@Woody Non ho visto il suo commento nell'enorme elenco di commenti qui, ma sì, questa è la soluzione. Perché non incorporarlo nella tua risposta?
BT,

93

È possibile impedire l'attivazione di dragleave quando si trascina in un elemento figlio?

Sì.

#drop * {pointer-events: none;}

Quel CSS sembra essere abbastanza per Chrome.

Mentre lo usi con Firefox, #drop non dovrebbe avere nodi di testo direttamente (altrimenti c'è uno strano problema in cui un elemento "lascialo a se stesso" ), quindi suggerisco di lasciarlo con un solo elemento (ad esempio, usa un div all'interno #drop per mettere tutto dentro)

Ecco un jsfiddle che risolve l' esempio della domanda originale (non funzionante) .

Ho anche realizzato una versione semplificata biforcuta dall'esempio di @Theodore Brown, ma basata solo su questo CSS.

Tuttavia, non tutti i browser hanno implementato questo CSS: http://caniuse.com/pointer-events

Vedendo il codice sorgente di Facebook l'ho trovato pointer-events: none;diverse volte, tuttavia è probabilmente usato insieme a fallback di degrado aggraziati. Almeno è così semplice e risolve il problema per molti ambienti.


La proprietà pointer-events è la giusta soluzione per il futuro, ma sfortunatamente non funziona in IE8-IE10, che sono ancora ampiamente utilizzati. Inoltre, dovrei sottolineare che l'attuale jsFiddle non funziona nemmeno in IE11, poiché non aggiunge i listener di eventi necessari e la prevenzione del comportamento predefinita.
Theodore Brown,

2
L'utilizzo di pointer-events è davvero una buona risposta, ho faticato un po 'prima di scoprire da solo, che la risposta dovrebbe essere più alta.
floribon,

Un'opzione fantastica per quelli di noi abbastanza fortunati da non dover supportare i vecchi browser.
Luke Hansford,

7
E se i tuoi figli sono un pulsante? oO
David

9
funziona solo se non hai altri elementi del controller (modifica, elimina) all'interno dell'area, perché anche questa soluzione li blocca.
Zoltán Süle

45

Ecco, la soluzione Cross-Browser più semplice (sul serio):

jsfiddle <- prova a trascinare alcuni file nella casella

Puoi fare qualcosa del genere:

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

In poche parole, crei una "maschera" all'interno della zona di rilascio, con larghezza e altezza ereditate, posizione assoluta, che mostrerà solo quando inizia il dragover.
Quindi, dopo aver mostrato quella maschera, puoi fare il trucco attaccando gli altri trascina e rilascia eventi su di essa.

Dopo aver lasciato o lasciato cadere, basta nascondere di nuovo la maschera.
Semplice e senza complicazioni.

(Obs .: Consiglio di Greg Pettit - Devi essere sicuro che la maschera passi sull'intera scatola, incluso il bordo)


Non so perché, ma non funziona in modo coerente con Chrome. A volte lasciare l'area mantiene visibile la maschera.
Greg Pettit,

2
In realtà, è il confine. Avere la maschera che si sovrappone al bordo, senza bordi propri, e dovrebbe funzionare correttamente.
Greg Pettit,

1
Nota, il tuo jsfiddle ha un bug al suo interno, in drag_drop, dovresti rimuovere la classe hover su "#box" non "# box-a"
entropia

Questa è una buona soluzione, ma in qualche modo non ha funzionato per me. Alla fine ho capito una soluzione. Per quelli di voi che cercano qualcos'altro. potresti provare questo: github.com/bingjie2680/jquery-draghover
bingjie2680

No. Non voglio perdere il controllo sugli elementi figlio. Ho creato un semplice plugin jQuery che affronta il problema facendo il lavoro per noi. Controllare la mia risposta .
Luca Fagioli,

39

È stato un po 'di tempo dopo che questa domanda è stata posta e sono state fornite molte soluzioni (inclusi brutti hack).

Sono riuscito a risolvere lo stesso problema che ho avuto di recente grazie alla risposta in questa risposta e ho pensato che potesse essere utile a qualcuno che arriva su questa pagina. L'idea è quella di memorizzare il evenet.targetin ondrageenterogni esso viene chiamato su qualsiasi degli elementi padre o figlio. Quindi ondragleavecontrolla se il target attuale ( event.target) è uguale all'oggetto in cui sei stato memorizzato ondragenter.

L'unico caso in cui questi due sono corrispondenti è quando il trascinamento lascia la finestra del browser.

Il motivo per cui questo funziona bene è quando il mouse lascia un elemento (dire el1) ed entra in un altro elemento (dire el2), prima el2.ondragenterviene chiamato e poi el1.ondragleave. Solo quando il trascinamento esce / entra nella finestra del browser, event.targetsarà ''in entrambi el2.ondragenter e el1.ondragleave.

Ecco il mio esempio di lavoro. L'ho provato su IE9 +, Chrome, Firefox e 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;
    };
})();

Ed ecco una semplice pagina 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>

Con uno stile adeguato ciò che ho fatto è di rendere il div interno (# file-drop-area) molto più grande ogni volta che un file viene trascinato nello schermo in modo che l'utente possa facilmente rilasciare i file nella posizione corretta.


4
Questa è la soluzione migliore, è migliore del contatore (soprattutto se si delegano eventi) e funziona anche con bambini trascinabili.
Fred,

3
Questo non aiuta il problema degli eventi duplicati di dragenter
BT,

Questo funziona per "bambini" che sono pseudo-elementi o pseudo-classi css? Non ci sono riuscito, ma forse stavo sbagliando.
Josh Bleecher Snyder,

1
A partire da marzo 2020 ho avuto la fortuna anche con questa soluzione. Nota: alcuni linter potrebbero lamentarsi dell'uso di ==over ===. Stai confrontando i riferimenti agli oggetti target dell'evento, quindi ===funziona bene.
Alex

33

Il modo "giusto" per risolvere questo problema è disabilitare gli eventi puntatore su elementi figlio del target di rilascio (come nella risposta di @ HD). Ecco un jsFiddle che ho creato che dimostra questa tecnica . Sfortunatamente, questo non funziona nelle versioni di Internet Explorer precedenti a IE11, poiché non supportavano eventi puntatore .

Per fortuna, sono riuscito a trovare una soluzione alternativa, che fa il lavoro nelle vecchie versioni di IE. Fondamentalmente, comporta l'identificazione e l'ignoranza di dragleaveeventi che si verificano trascinando elementi figlio. Poiché l' dragenterevento viene generato sui nodi figlio prima dragleavedell'evento sul padre, è possibile aggiungere listener di eventi separati a ciascun nodo figlio che aggiunge o rimuove una classe "ignore-drag-leave" dalla destinazione di rilascio. Quindi il dragleavelistener di eventi della destinazione di rilascio può semplicemente ignorare le chiamate che si verificano quando esiste questa classe. Ecco un jsFiddle che dimostra questa soluzione alternativa . È testato e funziona su Chrome, Firefox e IE8 +.

Aggiornare:

Ho creato un jsFiddle che dimostra una soluzione combinata utilizzando il rilevamento delle funzionalità, in cui gli eventi del puntatore vengono utilizzati se supportati (attualmente Chrome, Firefox e IE11) e il browser torna ad aggiungere eventi ai nodi figlio se il supporto degli eventi del puntatore non è disponibile (IE8 -10).


Questa risposta mostra una soluzione alternativa per l'attivazione indesiderata ma trascura la domanda "È possibile impedire l'attivazione del dragleave quando si trascina in un elemento figlio?" interamente.
HD

Il comportamento può diventare strano quando si trascina un file dall'esterno del browser. In Firefox ho "Inserimento figlio -> Inserimento genitore -> Abbandono figlio -> Inserimento figlio -> Abbandono figlio" senza lasciare il genitore, che è uscito con la classe "over". Il vecchio IE avrebbe bisogno di una sostituzione attachEvent per addEventListener.
HD

Tale soluzione dipende fortemente dal gorgogliamento, il "falso" in tutto addEventListener dovrebbe essere enfatizzato come essenziale (sebbene sia il comportamento predefinito), dal momento che molte persone potrebbero non saperlo.
HD

Quel singolo trascinabile aggiunge un effetto al dropzone che non appare quando il dropzone viene usato per altri oggetti trascinabili che non attivano l'evento dragstart. Forse usare tutto come dropzone per l'effetto del trascinamento mantenendo la vera dropzone con altri gestori lo farebbe.
HD

1
@HD Ho aggiornato la mia risposta con le informazioni sull'uso della proprietà CSS pointer-events per impedire che l' dragleaveevento si attivi in ​​Chrome, Firefox e IE11 +. Ho anche aggiornato la mia altra soluzione alternativa per supportare IE8 e IE9, oltre a IE10. È intenzionale che l'effetto dropzone venga aggiunto solo quando si trascina il collegamento "Trascina". Altri possono sentirsi liberi di cambiare questo comportamento secondo necessità per supportare i loro casi d'uso.
Theodore Brown,

14

Il problema è che l' dragleaveevento viene generato quando il mouse si posiziona davanti all'elemento figlio.

Ho provato vari metodi di controllo per vedere se l' e.targetelemento è uguale thisall'elemento, ma non sono riuscito a ottenere alcun miglioramento.

Il modo in cui ho risolto questo problema è stato un po 'un trucco, ma funziona 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
Grazie! Tuttavia, non riesco a farlo funzionare in Chrome. Potresti fornire un violino funzionante del tuo hack?
pimvdb,

3
Stavo pensando di farlo controllando anche i coords. Mi hai fatto gran parte del lavoro, grazie :). Ho dovuto apportare alcune modifiche:if (e.x >= (rect.left + rect.width) || e.x <= rect.left || e.y >= (rect.top + rect.height) || e.y <= rect.top)
Christof

1
Non funzionerà in Chrome perché l'evento non ha e.xe e.y.
Hengjie,

Mi piace questa soluzione. Non ha funzionato prima in Firefox. Ma se sostituisci ex con e.clientX e ey con e.clientY funziona. Funziona anche in Chrome.
Nicole Stutz,

Non ha funzionato in Chrome per me, né ha fatto ciò che Chris o Daniel Stuts hanno suggerito
Timo Huovinen,

14

se stai usando HTML5, puoi ottenere il client genitoreRetto:

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

Quindi in parent.dragleave ():

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

ecco un jsfiddle


2
Risposta eccellente.
broc.seib,

Grazie amico, mi piace il semplice approccio javascript.
Tim Gerhard,

Quando si utilizza elementi con border-radius, spostare il puntatore vicino all'angolo in realtà lascerebbe l'elemento, ma questo codice penserebbe ancora che siamo dentro (abbiamo lasciato l'elemento ma siamo ancora nel rettangolo di delimitazione). Quindi il dragleavegestore dell'evento non verrà chiamato affatto.
Jay Dadhania,

11

Una soluzione molto semplice è utilizzare la pointer-eventsproprietà CSS . Basta impostare il valore nonesu dragstart su ogni elemento figlio. Questi elementi non attiveranno più eventi relativi al mouse, quindi non cattureranno il mouse su di essi e quindi non attiveranno il dragleave sul genitore.

Non dimenticare di reimpostare questa proprietà al autotermine del trascinamento;)


11

Questa soluzione abbastanza semplice funziona finora per me, supponendo che il tuo evento sia collegato a ciascun elemento di trascinamento singolarmente.

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

Questo è fantastico! grazie per aver condiviso @kenneth, mi hai salvato la giornata! Molte delle altre risposte si applicano solo se si dispone di un'unica zona trascinabile.
Antoni,

Per me funziona in Chrome e Firefox, ma l'id non funziona in Edge. Si prega di consultare: jsfiddle.net/iwiss/t9pv24jo
iwis

8

Soluzione molto semplice:

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

Questo non sembra funzionare in Safari 12.1: jsfiddle.net/6d0qc87m
Adam Taylor

7

Puoi risolverlo in Firefox con un po 'di ispirazione dal codice sorgente di 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');
        }
    }

}

Sfortunatamente non funziona in Chrome perché relatedTargetsembra non esistere negli dragleaveeventi e presumo che tu stia lavorando in Chrome perché il tuo esempio non ha funzionato in Firefox. Ecco una versione con il codice sopra implementato.


Grazie mille, ma in effetti è Chrome che sto cercando di risolvere questo problema.
pimvdb,

5
@pimvdb Vedo che hai registrato un bug , lascerò un riferimento qui nel caso in cui qualcuno trovi questa risposta.
robertc,

L'ho fatto davvero, ma ho dimenticato di aggiungere un link qui. Grazie per averlo fatto.
pimvdb,

Il bug di Chrome è stato corretto nel frattempo.
phk,

7

Ed eccola qui, una soluzione per 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!');
                    }
                })

Va in «if (typeof event.clientX === 'undefined')»?
Aldekein,

1
Ha funzionato bene ma potrebbe esserci un'altra finestra sul browser, quindi ottenere la posizione del mouse e confrontarla con l'area dello schermo rettangolare non è sufficiente.
HD

Sono d'accordo con @HD Inoltre, questo causerà problemi quando l'elemento ha grandi border-radiuscome ho spiegato nel mio commento sulla risposta di @ azlar sopra.
Jay Dadhania,

7

Ecco un'altra soluzione che utilizza 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');
   }
}

Spero che funzioni, ecco un violino .


3
Questo non ha funzionato per me fuori dalla scatola. Ho dovuto usare event.clientX, event.clientYinvece, poiché sono relativi alla finestra e non alla pagina. Spero che aiuti un'altra anima persa là fuori.
Jon Abrams,

7

Una soluzione semplice consiste nell'aggiungere la regola CSS pointer-events: noneal componente figlio per impedire l'attivazione di ondragleave. Vedi esempio:

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>


Ho avuto lo stesso problema e la tua soluzione funziona alla grande. Suggerimento per gli altri, questo è stato il mio caso: se l'elemento figlio che si sta tentando di ignorare l' evento di trascinamento è il clone dell'elemento trascinato (nel tentativo di ottenere un'anteprima visiva), è possibile utilizzare questo dragstart interno durante la creazione del clone :dragged = event.target;clone = dragged.cloneNode();clone.style.pointerEvents = 'none';
Scaramouche,

4

Non sono sicuro se questo browser incrociato, ma ho testato in Chrome e risolve il mio problema:

Voglio trascinare e rilasciare un file su tutta la pagina, ma la mia foglia di trascinamento viene attivata quando trascino l'elemento figlio. La mia correzione era guardare la xey del mouse:

ho un div che si sovrappone alla mia intera pagina, quando la pagina viene caricata la nascondo.

quando trascini sopra il documento, lo mostro, e quando lo fai cadere sul genitore lo gestisce, e quando lasci il genitore, controllo 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 ma intelligente. Mi piace. Ha funzionato per me.
cupcakekid,

1
Oh, come vorrei che questa risposta avesse più voti! Grazie
Kirby,

4

Ho scritto una piccola libreria chiamata Dragster per gestire questo problema esatto, funziona ovunque tranne che silenziosamente non fa nulla in IE (che non supporta i costruttori di eventi DOM, ma sarebbe abbastanza facile scrivere qualcosa di simile usando gli eventi personalizzati di jQuery)


Molto utile (almeno per me dove mi interessa solo Chrome).
M Katz,

4

Stavo avendo lo stesso problema e ho provato ad usare la soluzione pk7s. Ha funzionato ma potrebbe essere fatto un po 'meglio senza elementi dom aggiuntivi.

Fondamentalmente l'idea è la stessa: aggiungi un overlay invisibile extra sull'area trascinabile. Facciamolo solo senza elementi dom aggiuntivi. Ecco la parte in cui gli pseudo-elementi CSS entrano in gioco.

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

Questa regola dopo creerà un overlay completamente coperto per l'area trascinabile.

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

Ecco la soluzione completa: http://jsfiddle.net/F6GDq/8/

Spero che aiuti chiunque abbia lo stesso problema.


1
Occasionalmente non funziona su Chrome (non rileva correttamente la foglia di drago)
Timo Huovinen

4

Basta controllare se l'elemento trascinato sopra è un bambino, se lo è, quindi non rimuovere la classe di stile "dragover". Abbastanza semplice e funziona per me:

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

Mi sembra la soluzione più semplice e ha risolto il mio problema. Grazie!
Francesco Marchetti-Stasi,

3

Mi sono imbattuto nello stesso problema ed ecco la mia soluzione - che penso sia molto più semplice di quanto sopra. Non sono sicuro che sia crossbrowser (potrebbe dipendere anche da un ordine di bolle)

Userò jQuery per semplicità, ma la soluzione dovrebbe essere indipendente dal framework.

L'evento si trasforma in genitore in entrambi i modi, come indicato:

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

Alleghiamo eventi

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

E questo è tutto. :) funziona perché anche se onEnter sui fuochi secondari prima di onLeave sul genitore, lo ritardiamo leggermente invertendo l'ordine, quindi la classe viene rimossa per prima, quindi raccolta dopo un millisecondo.


L'unica cosa che fa questo frammento è impedire che la classe "hovered" venga rimossa riapplicandola al successivo ciclo di tick (rendendo inutile l'evento "dragleave").
null

Non è inutile. Se lasci il genitore funzionerà come previsto. Il potere di questa soluzione è la sua semplicità, non è l'ideale o il migliore che ci sia. La soluzione migliore sarebbe quella di contrassegnare un invio su un figlio, nel controllo onleave se siamo appena entrati figlio, e in caso contrario non attivare l'evento di congedo. Avrà bisogno di test, protezioni extra, controllo dei nipoti, ecc.
Marcin Raczkowski,

3

Ho scritto un modulo drag-and-drop chiamato drip-drop che risolve questo strano comportamento, tra gli altri. Se stai cercando un buon modulo di trascinamento della selezione di basso livello che puoi utilizzare come base per qualsiasi cosa (caricamento di file, trascinamento della selezione in-app, trascinamento da o verso fonti esterne), dovresti controllare questo uscita del modulo:

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

Ecco come faresti quello che stai cercando di fare in gocciolamento:

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

Per fare questo senza una libreria, la tecnica del contatore è quella che ho usato in gocciolamento, anche se la risposta con il punteggio più alto manca passi importanti che causeranno la rottura delle cose per tutto tranne il primo rilascio. Ecco come farlo correttamente:

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 soluzione di lavoro alternativa, un po 'più semplice.

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

Questo non sembra funzionare sempre in Chrome. Occasionalmente ricevo clientXsopra 0 quando il mouse è fuori dalla scatola. Certo, i miei elementi sonoposition:absolute
Hengjie,

Succede sempre o solo qualche volta? Perché se il mouse si muove troppo velocemente (ad es. Fuori dalla finestra), si potrebbero ottenere valori errati.
Profet

Succede il 90% delle volte. Ci sono casi rari (1 su 10 volte) in cui riesco a raggiungere lo 0. Riproverò a spostare il mouse più lentamente, ma non potrei dire che mi stavo muovendo rapidamente (forse quella che chiameresti velocità normale).
Hengjie,

2

Dopo aver trascorso così tante ore ho avuto il suggerimento di funzionare esattamente come previsto. Volevo fornire uno spunto solo quando i file venivano trascinati, e documentando dragover, dragleave stava causando tremori dolorosi sul browser Chrome.

È così che l'ho risolto, fornendo anche spunti adeguati per l'utente.

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

Ho avuto un problema simile: il mio codice per nascondere la dropzone sull'evento dragleave per body è stato lanciato in modo contativo quando si passava il mouse su elementi figlio rendendo lo sfarfallio della dropzone in Google Chrome.

Sono stato in grado di risolvere questo problema programmando la funzione per nascondere dropzone invece di chiamarla subito. Quindi, se viene lanciato un altro dragover o dragleave, la chiamata di funzione programmata viene annullata.

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

Questo è quello che ho finito anche per fare, ma è ancora impreciso.
aprono il

1

L' evento " dragleave " viene generato quando il puntatore del mouse esce dall'area di trascinamento del contenitore di destinazione.

Il che ha molto senso, poiché in molti casi solo il genitore può essere lasciato cadere e non i discendenti. Penso che event.stopPropogation () avrebbe dovuto gestire questo caso, ma sembra che non faccia il trucco.

Sopra menzionato alcune soluzioni sembrano funzionare per la maggior parte dei casi, ma falliscono nel caso di quei figli che non supportano eventi dragenter / dragleave , come iframe .

1 soluzione alternativa è verificare event.relatedTarget e verificare se risiede all'interno del contenitore, quindi ignorare l' evento dragleave come ho fatto qui:

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

Puoi trovare un violino funzionante qui !


1

Risolto ..!

Dichiarare qualsiasi array per es:

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

Non c'è bisogno di preoccuparsi degli elementi figlio.


1

Ho lottato molto con questo, anche dopo aver letto tutte queste risposte, e ho pensato che potrei condividere la mia soluzione con te, perché ho pensato che potesse essere uno degli approcci più semplici, anche se in qualche modo diverso. Il mio pensiero era semplicemente omettere dragleavecompletamente l'ascoltatore di eventi e codificare il comportamento di dragleave con ogni nuovo evento dragenter attivato, assicurandomi al contempo che gli eventi dragenter non venissero generati inutilmente.

Nel mio esempio di seguito, ho una tabella, in cui desidero poter scambiare tra loro i contenuti delle righe della tabella tramite l'API di trascinamento della selezione. Su dragenter, una classe CSS deve essere aggiunta all'elemento riga in cui si sta attualmente trascinando l'elemento, per evidenziarlo e su dragleave, questa classe deve essere rimossa.

Esempio:

Tabella HTML molto semplice:

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

E la funzione di gestore di eventi DragEnter, aggiunto su ogni cella di una tabella (a parte dragstart, dragover, drop, e dragendgestori, che non sono specifici a questa domanda, in modo da non copiate qui):

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

Commenti di base lasciati nel codice, in modo che questo codice sia adatto anche ai principianti. Spero che questo ti possa aiutare! Nota che dovrai ovviamente aggiungere tutti i listener di eventi che ho menzionato sopra in ogni cella della tabella affinché questo funzioni.


0

Ecco un altro approccio basato sulla tempistica degli eventi.

L' dragenterevento inviato dall'elemento figlio può essere catturato dall'elemento padre e si verifica sempre prima dell'elemento dragleave. Il tempismo tra questi due eventi è davvero breve, più breve di qualsiasi possibile azione del mouse umano. Quindi, l'idea è quella di memorizzare il momento in cui dragenteraccade e filtrare gli dragleaveeventi che si verificano "non troppo rapidamente" dopo ...

Questo breve esempio funziona su Chrome e 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 ..

Perché non provi usando drop invece di dragleave . Ha funzionato per me. spero che questo risolva il tuo problema.

Si prega di controllare il 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
Se l'utente cambia idea e trascina il file fuori dal browser, questo rimarrà rosso.
ZachB,

0

È possibile utilizzare un timeout con una transitioningbandiera e ascoltare l'elemento in alto. dragenter/ dragleavedagli eventi figlio si diffonderà nel contenitore.

Poiché dragentersull'elemento figlio viene attivato prima dragleavedel contenitore, imposteremo lo spettacolo di bandiera in transizione per 1 ms ... l' dragleaveascoltatore controllerà la bandiera prima che sia attivo 1ms.

Il flag sarà vero solo durante le transizioni verso elementi figlio e non sarà vero quando si passa a un elemento padre (del contenitore)

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

È necessario rimuovere gli eventi del puntatore per tutti gli oggetti figlio della destinazione di trascinamento.

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) {
                //
            }
        }
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.