HTML5-Dragleave wird ausgelöst, wenn ein untergeordnetes Element bewegt wird


300

Das Problem, das ich habe, ist, dass das dragleaveEreignis eines Elements ausgelöst wird, wenn ein untergeordnetes Element dieses Elements bewegt wird. Wird auch dragenternicht ausgelöst, wenn Sie das übergeordnete Element erneut zurückfahren.

Ich habe eine vereinfachte Geige gemacht: 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>

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

Was es tun soll, ist den Benutzer zu benachrichtigen, indem der Tropfen divrot gezogen wird, wenn etwas dorthin gezogen wird. Dies funktioniert, aber wenn Sie in das pKind ziehen, wird das dragleavegefeuert und das divist nicht mehr rot. Wenn Sie zum Tropfen zurückkehren, wird er divauch nicht wieder rot. Es ist notwendig, sich vollständig aus dem Tropfen zu bewegen divund ihn wieder hineinzuziehen, um ihn rot zu machen.

Ist es möglich zu verhindern, dass dragleavebeim Ziehen in ein untergeordnetes Element ausgelöst wird?

Update 2017: TL; DR, Suchen Sie nach CSS, pointer-events: none;wie in der Antwort von @ HD unten beschrieben, die in modernen Browsern und IE11 funktioniert.


Der gemeldete Fehler pimvdb ist seit Mai 2012 in Webkit noch vorhanden. Ich habe dem entgegengewirkt, indem ich auch eine Klasse in Dragover hinzugefügt habe, die nicht annähernd nett ist, da sie so oft ausgelöst wird, aber das Problem ein wenig zu beheben scheint.
Ajm

2
@ajm: Danke, das funktioniert bis zu einem gewissen Grad. In Chrome gibt es jedoch einen Blitz, wenn Sie das untergeordnete Element betreten oder verlassen, vermutlich weil dragleavees in diesem Fall immer noch ausgelöst wird.
Pimvdb

Ich habe einen jQuery UI-Fehler geöffnet. Upvotes sind willkommen, damit sie entscheiden können, Ressourcen darauf zu setzen
fguillen

@fguillen: Es tut mir leid, aber das hat nichts mit der jQuery-Benutzeroberfläche zu tun. Tatsächlich wird jQuery nicht einmal benötigt, um den Fehler auszulösen. Ich habe bereits einen WebKit-Fehler gemeldet, aber es gibt noch kein Update.
Pimvdb

2
@pimvdb Warum akzeptierst du die Antwort von HD nicht?
TylerH

Antworten:


337

Sie müssen nur einen Referenzzähler behalten, ihn erhöhen, wenn Sie einen Dragenter erhalten, und verringern, wenn Sie einen Dragleave erhalten. Wenn der Zähler auf 0 steht, entfernen Sie die Klasse.

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

Hinweis: Setzen Sie im Drop-Ereignis den Zähler auf Null zurück und löschen Sie die hinzugefügte Klasse.

Sie können es hier ausführen


8
OMG das ist die naheliegendste Lösung und hatte nur EINE Stimme ... Komm schon Leute, du kannst es besser machen. Ich habe darüber nachgedacht, aber nachdem ich den Grad der Raffinesse der ersten Antworten gesehen hatte, hätte ich es fast verworfen. Hattest du irgendwelche Nachteile?
Arthur Corenzan

11
Dies funktionierte nicht, wenn die Kante des zu ziehenden Elements die Kante eines anderen ziehbaren Elements berührt. Zum Beispiel eine sortierbare Liste; Wenn Sie das Element nach unten über das nächste ziehbare Element ziehen, wird der Zähler nicht auf 0 zurückgesetzt, sondern bleibt bei 1 hängen. Wenn ich es jedoch seitlich aus einem anderen ziehbaren Element ziehe, funktioniert es. Ich ging mit den pointer-events: noneauf die Kinder. Wenn ich mit dem Ziehen beginne, füge ich eine Klasse mit dieser Eigenschaft hinzu. Wenn das Ziehen vorbei ist, entferne ich die Klasse. Funktionierte gut auf Safari und Chrome, aber nicht auf Firefox.
Arthur Corenzan

13
Dies funktionierte für mich in allen Browsern, außer Firefox. Ich habe eine neue Lösung, die in meinem Fall überall funktioniert. Beim ersten dragenterspeichere ich event.currentTargetin einer neuen Variablen dragEnterTarget. Solange dragEnterTargetfestgelegt, ignoriere ich weitere dragenterEreignisse, da sie von Kindern stammen. Auf jeden dragleaveFall überprüfe ich dragEnterTarget === event.target. Wenn dies falsch ist, wird das Ereignis ignoriert, da es von einem Kind ausgelöst wurde. Wenn dies wahr ist , ich zurückgesetzt dragEnterTargetzu undefined.
Pipo

3
Tolle Lösung. Ich musste den Zähler in der Funktion, die das Akzeptieren des Abwurfs behandelt, auf 0 zurücksetzen, da sonst nachfolgende Drags nicht wie erwartet funktionierten.
Liam Johnston

2
@ Woody Ich habe seinen Kommentar in der riesigen Liste der Kommentare hier nicht gesehen, aber ja, das ist die Lösung. Warum nehmen Sie das nicht in Ihre Antwort auf?
BT

93

Ist es möglich zu verhindern, dass Dragleave beim Ziehen in ein untergeordnetes Element ausgelöst wird?

Ja.

#drop * {pointer-events: none;}

Dieses CSS scheint für Chrome ausreichend zu sein.

Während der Verwendung mit Firefox sollte das #drop keine Textknoten direkt haben (andernfalls gibt es ein seltsames Problem, bei dem ein Element "sich selbst überlässt" ), daher empfehle ich, es nur mit einem Element zu belassen (z. B. ein div im Inneren verwenden) #drop um alles hinein zu legen)

Hier ist eine jsfiddle , die das ursprüngliche (gebrochene) Beispiel der Frage löst .

Ich habe auch eine vereinfachte Version erstellt, die aus dem @ Theodore Brown-Beispiel stammt, aber nur auf diesem CSS basiert.

Nicht alle Browser haben dieses CSS implementiert: http://caniuse.com/pointer-events

Wenn ich den Facebook-Quellcode sehe, kann ich ihn pointer-events: none;mehrmals finden, aber er wird wahrscheinlich zusammen mit anmutigen Degradations-Fallbacks verwendet. Zumindest ist es so einfach und löst das Problem für viele Umgebungen.


Die Eigenschaft pointer-events ist die richtige Lösung für die Zukunft, funktioniert jedoch leider nicht in IE8-IE10, die immer noch weit verbreitet sind. Außerdem sollte ich darauf hinweisen, dass Ihre aktuelle jsFiddle nicht einmal in IE11 funktioniert, da sie nicht die erforderlichen Ereignis-Listener und die Standard-Verhaltensprävention hinzufügt.
Theodore Brown

2
Die Verwendung von Zeigerereignissen ist in der Tat eine gute Antwort. Ich habe ein wenig gekämpft, bevor ich selbst herausgefunden habe, dass diese Antwort höher sein sollte.
Floribon

Fantastische Option für diejenigen von uns, die das Glück haben, keine alten Browser unterstützen zu müssen.
Luke Hansford

7
Was ist, wenn Ihre Kinder ein Knopf sind? oO
David

9
Es funktioniert nur, wenn Sie keine anderen Controller-Elemente (Bearbeiten, Löschen) im Bereich haben, da diese Lösung sie ebenfalls blockiert.
Zoltán Süle

45

Hier die einfachste Cross-Browser-Lösung (im Ernst):

jsfiddle <- versuchen Sie, eine Datei in die Box zu ziehen

Sie können so etwas tun:

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 wenigen Worten erstellen Sie eine "Maske" innerhalb der Dropzone mit geerbter Breite und Höhe und absoluter Position, die nur angezeigt wird, wenn der Dragover beginnt.
Nachdem Sie diese Maske gezeigt haben, können Sie den Trick ausführen, indem Sie die anderen Dragleave & Drop-Ereignisse anhängen.

Nach dem Verlassen oder Ablegen verstecken Sie die Maske einfach wieder.
Einfach und ohne Komplikationen.

(Hinweis: Greg Pettit-Rat - Sie müssen sicher sein, dass die Maske über der gesamten Box einschließlich des Randes schwebt.)


Ich weiß nicht warum, aber es funktioniert nicht konsistent mit Chrome. Manchmal bleibt die Maske sichtbar, wenn Sie den Bereich verlassen.
Greg Pettit

2
Eigentlich ist es die Grenze. Lassen Sie die Maske den Rand überlappen, ohne einen eigenen Rand, und es sollte in Ordnung sein.
Greg Pettit

1
Beachten Sie, dass Ihre jsfiddle einen Fehler enthält. In drag_drop sollten Sie die Hover-Klasse auf "#box" und nicht auf "# box-a" entfernen
Entropie

Dies ist eine schöne Lösung, aber irgendwie hat es bei mir nicht funktioniert. Ich finde endlich eine Problemumgehung heraus. Für diejenigen unter Ihnen, die etwas anderes suchen. Sie könnten dies versuchen: github.com/bingjie2680/jquery-draghover
bingjie2680

Nein, ich möchte nicht die Kontrolle über die untergeordneten Elemente verlieren. Ich habe ein einfaches jQuery-Plugin erstellt, das sich mit dem Problem befasst, das die Arbeit für uns erledigt. Überprüfen Sie meine Antwort .
Luca Fagioli

39

Es ist einige Zeit her, dass diese Frage gestellt wurde und viele Lösungen (einschließlich hässlicher Hacks) bereitgestellt werden.

Ich habe es geschafft, das gleiche Problem zu beheben, das ich kürzlich dank der Antwort in dieser Antwort hatte, und dachte, es könnte für jemanden hilfreich sein, der auf diese Seite gelangt. Die ganze Idee ist, das evenet.targetin ondrageenterjedem Mal zu speichern, wenn es auf einem der übergeordneten oder untergeordneten Elemente aufgerufen wird. Dann ondragleaveprüfen Sie, ob das aktuelle Ziel ( event.target) ist gleich das Objekt , das Sie in gespeichert ondragenter.

Der einzige Fall, in dem diese beiden übereinstimmen, ist, wenn Sie das Browserfenster verlassen.

Der Grund, warum dies gut funktioniert, ist, wenn die Maus ein Element verlässt (sagen wir el1) und ein anderes Element betritt (sagen wir el2), zuerst wird das el2.ondragenteraufgerufen und dann el1.ondragleave. Nur wenn das Ziehen das Browserfenster verlässt / betritt, event.targetwird ''in beiden el2.ondragenter und el1.ondragleave.

Hier ist mein Arbeitsprobe. Ich habe es auf IE9 +, Chrome, Firefox und Safari getestet.

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

Und hier ist eine einfache HTML-Seite:

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

Mit dem richtigen Styling habe ich das innere div (# file-drop-area) viel größer gemacht, wenn eine Datei auf den Bildschirm gezogen wird, damit der Benutzer die Dateien einfach an der richtigen Stelle ablegen kann.


4
Dies ist die beste Lösung, sie ist besser als der Zähler (insbesondere wenn Sie Ereignisse delegieren) und funktioniert auch mit ziehbaren Kindern.
Fred

3
Dies hilft nicht für das Problem der doppelten Dragenter-Ereignisse
BT

Funktioniert dies für "Kinder", die CSS-Pseudoelemente oder Pseudoklassen sind? Ich konnte es nicht schaffen, aber vielleicht habe ich es falsch gemacht.
Josh Bleecher Snyder

1
Ab März 2020 hatte ich auch mit dieser Lösung das beste Glück. Hinweis: Einige Linters beschweren sich möglicherweise über die Verwendung von ==Over ===. Sie vergleichen die Ereigniszielobjektreferenzen und ===funktionieren daher auch einwandfrei.
Alex

33

Der "richtige" Weg, um dieses Problem zu lösen, besteht darin, Zeigerereignisse für untergeordnete Elemente des Ablageziels zu deaktivieren (wie in der Antwort von @ HD). Hier ist eine von mir erstellte jsFiddle, die diese Technik demonstriert . Leider funktioniert dies in Versionen von Internet Explorer vor IE11 nicht, da Zeigerereignisse nicht unterstützt wurden .

Glücklicherweise konnte ich mit einer Vermeidung des Problems kommen , die tut Arbeit in alten Versionen von IE. Grundsätzlich geht es darum, dragleaveEreignisse zu identifizieren und zu ignorieren , die beim Ziehen über untergeordnete Elemente auftreten. Da das dragenterEreignis auf untergeordneten Knoten vor dem dragleaveEreignis auf dem übergeordneten Knoten ausgelöst wird , können jedem untergeordneten Knoten separate Ereignis-Listener hinzugefügt werden, die eine Klasse "Ignorieren-Ziehen-Verlassen" zum Ablageziel hinzufügen oder daraus entfernen. Dann kann der dragleaveEreignis-Listener des Drop-Ziels Aufrufe, die auftreten, wenn diese Klasse vorhanden ist, einfach ignorieren. Hier ist eine jsFiddle, die diese Problemumgehung demonstriert . Es ist getestet und funktioniert in Chrome, Firefox und IE8 +.

Aktualisieren:

Ich habe eine jsFiddle erstellt, die eine kombinierte Lösung mithilfe der Funktionserkennung demonstriert , bei der Zeigerereignisse verwendet werden, wenn sie unterstützt werden (derzeit Chrome, Firefox und IE11), und der Browser auf das Hinzufügen von Ereignissen zu untergeordneten Knoten zurückgreift, wenn die Unterstützung von Zeigerereignissen nicht verfügbar ist (IE8) -10).


Diese Antwort zeigt eine Problemumgehung für das unerwünschte Brennen, vernachlässigt jedoch die Frage "Ist es möglich zu verhindern, dass Dragleave beim Ziehen in ein untergeordnetes Element ausgelöst wird?" völlig.
HD

Das Verhalten kann beim Ziehen einer Datei von außerhalb des Browsers merkwürdig werden. In Firefox habe ich "Kind eingeben -> Eltern eingeben -> Kind verlassen -> Kind eingeben -> Kind verlassen", ohne das Elternteil zu verlassen, was mit der Klasse "über" übrig blieb. Der alte IE würde einen AttachEvent-Ersatz für den addEventListener benötigen.
HD

Diese Lösung hängt stark vom Sprudeln ab. Das "Falsche" in allen addEventListener sollte als wesentlich hervorgehoben werden (obwohl dies das Standardverhalten ist), da viele Leute möglicherweise nichts davon wissen.
HD

Diese einzelne ziehbare Datei fügt der Dropzone einen Effekt hinzu, der nicht angezeigt wird, wenn die Dropzone für andere ziehbare Objekte verwendet wird, die das Dragstart-Ereignis nicht auslösen. Vielleicht würde dies alles als Dropzone für den Effekt des Ziehens verwenden, während die reale Dropzone mit anderen Handlern beibehalten wird.
HD

1
@HD Ich habe meine Antwort mit Informationen zur Verwendung der CSS-Eigenschaft "Zeigerereignisse" aktualisiert, um zu verhindern, dass das dragleaveEreignis in Chrome, Firefox und IE11 + ausgelöst wird. Ich habe neben IE10 auch meine andere Problemumgehung aktualisiert, um IE8 und IE9 zu unterstützen. Es ist beabsichtigt, dass der Dropzone-Effekt nur beim Ziehen des Links "Drag me" hinzugefügt wird. Andere können dieses Verhalten nach Bedarf ändern, um ihre Anwendungsfälle zu unterstützen.
Theodore Brown

14

Das Problem ist, dass das dragleaveEreignis ausgelöst wird, wenn sich die Maus vor dem untergeordneten Element befindet.

Ich habe verschiedene Methoden ausprobiert, um festzustellen, ob das e.targetElement mit dem Element identisch thisist, konnte jedoch keine Verbesserung erzielen.

Die Art und Weise, wie ich dieses Problem behoben habe, war ein bisschen hacken, funktioniert aber zu 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
Vielen Dank! Ich kann dies jedoch nicht in Chrome zum Laufen bringen. Könnten Sie eine funktionierende Geige Ihres Hacks liefern?
Pimvdb

3
Ich dachte daran, indem ich auch die Koordinaten überprüfte. Du hast die meiste Arbeit für mich gemacht, danke :). Ich musste allerdings einige Anpassungen vornehmen:if (e.x >= (rect.left + rect.width) || e.x <= rect.left || e.y >= (rect.top + rect.height) || e.y <= rect.top)
Christof

1
In Chrome funktioniert es nicht, da das Ereignis kein e.xund hat e.y.
Hengjie

Ich mag diese Lösung. Hat in Firefox zuerst nicht funktioniert. Aber wenn Sie ex durch e.clientX und ey durch e.clientY ersetzen, funktioniert es. Funktioniert auch in Chrome.
Nicole Stutz

Hat für mich nicht in Chrom gearbeitet, auch nicht das, was Chris oder Daniel Stuts vorgeschlagen haben
Timo Huovinen

14

Wenn Sie HTML5 verwenden, können Sie den clientRect des Elternteils abrufen:

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

Dann in der parent.dragleave ():

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

Hier ist eine Geige


2
Hervorragende Antwort.
Broc.seib

Danke Kumpel, ich mag den einfachen Javascript-Ansatz.
Tim Gerhard

Bei Verwendung von Elementen mit border-radiushat das Bewegen des Zeigers in die Nähe der Ecke das Element tatsächlich verlassen , aber dieser Code würde immer noch denken, dass wir uns im Inneren befinden (wir haben das Element verlassen, befinden uns aber immer noch im Begrenzungsrechteck). Dann wird der dragleaveEvent-Handler überhaupt nicht aufgerufen.
Jay Dadhania

11

Eine sehr einfache Lösung ist die Verwendung der pointer-eventsCSS-Eigenschaft . Setzen Sie den Wert einfachnone beim Dragstart für jedes untergeordnete Element. Diese Elemente lösen keine mausbezogenen Ereignisse mehr aus, sodass sie nicht mehr mit der Maus darüber fahren und somit kein Dragleave auf dem übergeordneten Element auslösen .

Vergessen Sie nicht, diese Eigenschaft autobeim Beenden des Ziehens auf zurückzusetzen;)


11

Diese ziemlich einfache Lösung funktioniert bisher für mich, vorausgesetzt, Ihr Ereignis wird jedem Drag-Element einzeln zugeordnet.

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

Das ist toll! Danke, dass du @kenneth geteilt hast, du hast meinen Tag gerettet! Viele der anderen Antworten gelten nur, wenn Sie eine einzige abnehmbare Zone haben.
Antoni

Für mich funktioniert es in Chrome und Firefox, aber ID funktioniert nicht in Edge. Bitte sehen Sie: jsfiddle.net/iwiss/t9pv24jo
iwis

8

Sehr einfache Lösung:

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

Dies scheint in Safari 12.1 nicht zu funktionieren: jsfiddle.net/6d0qc87m
Adam Taylor

7

Sie können es in Firefox mit ein wenig Inspiration aus dem jQuery-Quellcode beheben :

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

    if (related !== this) {

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

        if (!inside) {

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

}

Leider funktioniert es in Chrome relatedTargetnicht, da es bei dragleaveEreignissen anscheinend nicht vorhanden ist , und ich gehe davon aus, dass Sie in Chrome arbeiten, weil Ihr Beispiel in Firefox nicht funktioniert hat. Hier ist eine Version mit dem oben implementierten Code implementiert.


Vielen Dank, aber in der Tat ist es Chrome, in dem ich versuche, dieses Problem zu lösen.
pimvdb

5
@pimvdb Ich sehe, dass Sie einen Fehler protokolliert haben. Ich werde hier nur einen Verweis darauf hinterlassen, falls jemand anderes auf diese Antwort stößt.
Robertc

Ich habe es zwar getan, aber ich habe vergessen, hier einen Link hinzuzufügen. Danke dafür.
Pimvdb

Der Chrome-Fehler wurde in der Zwischenzeit behoben.
Phk

7

Und los geht's, eine Lösung für 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!');
                    }
                })

Geht es in «if (typeof event.clientX === 'undefined')»?
Aldekein

1
Hat gut funktioniert, aber es könnte ein anderes Fenster über dem Browser geben, sodass es nicht ausreicht, die Mausposition zu ermitteln und sie mit dem rechteckigen Bildschirmbereich zu vergleichen.
HD

Ich stimme @HD zu. Dies führt auch zu Problemen, wenn das Element groß ist, border-radiuswie ich in meinem Kommentar zu der Antwort von @ azlar oben erläutert habe.
Jay Dadhania

7

Hier ist eine andere Lösung mit 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');
   }
}

Hoffe das funktioniert, hier ist eine Geige .


3
Das hat bei mir nicht sofort funktioniert. Ich musste event.clientX, event.clientYstattdessen verwenden, da sie relativ zum Ansichtsfenster und nicht zur Seite sind. Ich hoffe, das hilft einer anderen verlorenen Seele da draußen.
Jon Abrams

7

Eine einfache Lösung besteht darin, die CSS-Regel pointer-events: nonezur untergeordneten Komponente hinzuzufügen , um den Auslöser von zu verhindern ondragleave. Siehe Beispiel:

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>


Ich hatte das gleiche Problem und Ihre Lösung funktioniert großartig. Tipp für andere, dies war mein Fall: Wenn das untergeordnete Element, das Sie versuchen, das Drag- Ereignis zu ignorieren, der Klon des zu ziehenden Elements ist (um eine visuelle Vorschau zu erzielen), können Sie dies beim Erstellen des Klons in Dragstart verwenden :dragged = event.target;clone = dragged.cloneNode();clone.style.pointerEvents = 'none';
Scaramouche

4

Ich bin mir nicht sicher, ob es sich um einen Cross-Browser handelt, aber ich habe ihn in Chrome getestet und er löst mein Problem:

Ich möchte eine Datei über die gesamte Seite ziehen und ablegen, aber mein Dragleave wird ausgelöst, wenn ich über ein untergeordnetes Element ziehe. Meine Lösung bestand darin, das x und y der Maus zu betrachten:

Ich habe ein Div, das meine gesamte Seite überlagert. Wenn die Seite geladen wird, verstecke ich sie.

Wenn Sie über ein Dokument ziehen, zeige ich es, und wenn Sie auf das übergeordnete Dokument fallen, wird es behandelt, und wenn Sie das übergeordnete Dokument verlassen, überprüfe ich x und 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 aber schlau. Ich mag. Hat für mich gearbeitet.
Cupcakekid

1
Oh, wie gerne hätte diese Antwort mehr Stimmen! Vielen Dank
Kirby

4

Ich habe eine kleine Bibliothek namens Dragster geschrieben , um genau dieses Problem zu lösen. Sie funktioniert überall, außer im IE stillschweigend nichts zu tun (was DOM-Ereigniskonstruktoren nicht unterstützt, aber es wäre ziemlich einfach, mit den benutzerdefinierten Ereignissen von jQuery etwas Ähnliches zu schreiben).


Sehr nützlich (zumindest für mich, wo ich mich nur für Chrome interessiere).
M Katz

4

Ich hatte das gleiche Problem und versuchte, die pk7s-Lösung zu verwenden. Es hat funktioniert, aber es könnte ein bisschen besser ohne zusätzliche Dom-Elemente gemacht werden.

Grundsätzlich ist die Idee dieselbe - fügen Sie eine zusätzliche nicht sichtbare Überlagerung über dem ablegbaren Bereich hinzu. Lassen Sie uns dies nur ohne zusätzliche dom-Elemente tun. Hier ist die Rolle, in der CSS-Pseudoelemente zum Einsatz kommen.

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

Diese Nachregel erstellt eine vollständig abgedeckte Überlagerung für den ablegbaren Bereich.

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

Hier ist die vollständige Lösung: http://jsfiddle.net/F6GDq/8/

Ich hoffe es hilft jedem mit dem gleichen Problem.


1
Funktioniert gelegentlich nicht auf Chrom (fängt Dragleave nicht richtig auf)
Timo Huovinen

4

Überprüfen Sie einfach, ob das gezogene Element ein untergeordnetes Element ist. Wenn dies der Fall ist, entfernen Sie nicht Ihre Stilklasse "Dragover". Ziemlich einfach und funktioniert bei mir:

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

Sieht für mich nach der einfachsten Lösung aus und hat mein Problem gelöst. Vielen Dank!
Francesco Marchetti-Stasi

3

Ich bin auf dasselbe Problem gestoßen und hier ist meine Lösung - die meiner Meinung nach viel einfacher ist als oben. Ich bin mir nicht sicher, ob es sich um einen Crossbrowser handelt (hängt möglicherweise von der Reihenfolge des Sprudelns ab).

Ich werde der Einfachheit halber jQuery verwenden, aber die Lösung sollte rahmenunabhängig sein.

Das Ereignis sprudelt so oder so zu den Eltern:

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

Wir fügen Ereignisse bei

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

Und das war's auch schon. :) es funktioniert, weil, obwohl onEnter on child vor onLeave on parent ausgelöst wird, die Reihenfolge etwas verzögert wird, sodass die Klasse zuerst entfernt und nach einer Millisekunde erneut angewendet wird.


Das einzige, was dieses Snippet bewirkt, ist zu verhindern, dass die 'schwebende' Klasse entfernt wird, indem es beim nächsten Tick-Zyklus erneut angewendet wird (wodurch das 'Dragleave'-Ereignis unbrauchbar wird).
null

Es ist nicht nutzlos. Wenn Sie die Eltern verlassen, funktioniert es wie erwartet. Die Stärke dieser Lösung liegt in ihrer Einfachheit, sie ist nicht ideal oder das Beste, was es gibt. Eine bessere Lösung wäre, eine Eingabe für ein Kind zu markieren, bei der Überprüfung zu überprüfen, ob wir gerade ein Kind eingegeben haben, und wenn nicht, ein Urlaubsereignis auszulösen. Es wird jedoch Tests, zusätzliche Wachen, das Suchen nach Enkelkindern usw. brauchen
Marcin Raczkowski

3

Ich habe ein Drag-and-Drop-Modul namens Drip-Drop geschrieben, das unter anderem dieses verrückte Verhalten behebt. Wenn Sie nach einem guten einfachen Drag-and-Drop-Modul suchen, das Sie als Grundlage für alles verwenden können (Datei-Upload, In-App-Drag-and-Drop, Ziehen von oder zu externen Quellen), sollten Sie dies überprüfen Modul aus:

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

So würden Sie das tun, was Sie in Drip-Drop versuchen:

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

Um dies ohne Bibliothek zu tun, habe ich die Zählertechnik in Drip-Drop verwendet, wobei die am höchsten bewertete Antwort wichtige Schritte übersieht, die dazu führen, dass die Dinge für alles außer dem ersten Tropfen kaputt gehen. So geht's richtig:

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

Eine alternative Arbeitslösung, etwas einfacher.

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

Dies scheint in Chrome nicht immer zu funktionieren. Ich würde gelegentlich clientXüber 0 erhalten, wenn sich die Maus außerhalb der Box befindet. Zugegeben, meine Elemente sindposition:absolute
Hengjie

Kommt es die ganze Zeit vor oder nur manchmal? Denn wenn sich die Maus zu schnell bewegt (z. B. außerhalb des Fensters), erhalten Sie möglicherweise falsche Werte.
Profet

Es passiert 90% der Zeit. Es gibt seltene Fälle (1 von 10 Mal), in denen ich es auf 0 bringen kann. Ich werde erneut versuchen, die Maus langsamer zu bewegen, aber ich konnte nicht sagen, dass ich mich schnell bewegte (vielleicht das, was Sie als normale Geschwindigkeit bezeichnen würden).
Hengjie

2

Nachdem ich so viele Stunden verbracht hatte, bekam ich diesen Vorschlag, genau wie beabsichtigt zu arbeiten. Ich wollte nur dann einen Hinweis geben, wenn Dateien über das Drag & Drop-Dokument gezogen wurden und Dragleave im Chrome-Browser schmerzhaftes Flimmern verursachte.

Auf diese Weise habe ich es gelöst und auch die richtigen Hinweise für den Benutzer eingegeben.

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

Ich hatte ein ähnliches Problem: Mein Code zum Ausblenden der Dropzone beim Dragleave-Ereignis für den Körper wurde beim Schweben untergeordneter Elemente, die die Dropzone in Google Chrome flackern ließen, kontingent ausgelöst.

Ich konnte dies lösen, indem ich die Funktion zum Ausblenden der Dropzone plante, anstatt sie sofort aufzurufen. Wenn dann ein anderer Dragover oder Dragleave ausgelöst wird, wird der geplante Funktionsaufruf abgebrochen.

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

Das habe ich auch getan, aber es ist immer noch lückenhaft.
Mpen

1

Das Ereignis " dragleave " wird ausgelöst, wenn der Mauszeiger den Ziehbereich des Zielcontainers verlässt.

Das ist sehr sinnvoll, da in vielen Fällen nur die Eltern und nicht die Nachkommen fallen gelassen werden können . Ich denke event.stopPropogation () hätte diesen Fall behandeln sollen, aber es scheint, als würde es nicht den Trick machen.

Die oben genannten Lösungen scheinen in den meisten Fällen zu funktionieren, schlagen jedoch bei den untergeordneten Elementen fehl , die Dragenter- / Dragleave- Ereignisse wie iframe nicht unterstützen .

Eine Problemumgehung besteht darin, das event.relatedTarget zu überprüfen und zu überprüfen, ob es sich im Container befindet. Ignorieren Sie dann das Dragleave- Ereignis, wie ich es hier getan habe:

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

Sie können eine Arbeits Geige finden hier !


1

Gelöst ..!

Deklarieren Sie ein beliebiges Array, zum Beispiel:

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

Sie müssen sich keine Sorgen um untergeordnete Elemente machen.


1

Ich hatte viel damit zu kämpfen, selbst nachdem ich all diese Antworten durchgelesen hatte, und dachte, ich könnte meine Lösung mit Ihnen teilen, weil ich dachte, es könnte einer der einfacheren Ansätze sein, allerdings etwas anders. Mein Gedanke war, den dragleaveEreignis-Listener einfach komplett wegzulassen und das Dragleave-Verhalten bei jedem neuen ausgelösten Dragenter-Ereignis zu codieren, während sichergestellt wurde, dass Dragenter-Ereignisse nicht unnötig ausgelöst werden.

In meinem Beispiel unten habe ich eine Tabelle, in der ich den Inhalt von Tabellenzeilen über die Drag & Drop-API miteinander austauschen möchte. Ein dragenter, dem Zeilenelement, in das Sie gerade Ihr Element ziehen, wird eine CSS-Klasse hinzugefügt, um es hervorzuheben, und dragleavediese Klasse wird entfernt.

Beispiel:

Sehr einfache HTML-Tabelle:

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

Und die dragenter- Event - Handler - Funktion, hinzugefügt auf jede Tabellenzelle (beiseite dragstart, dragover, drop, und dragendHandler, die auf diese Frage nicht spezifisch sind, so hier nicht kopiert):

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

Sehr einfache Kommentare im Code, so dass dieser Code auch für Anfänger geeignet ist. Hoffe das wird dir helfen! Beachten Sie, dass Sie natürlich alle oben erwähnten Ereignis-Listener zu jeder Tabellenzelle hinzufügen müssen, damit dies funktioniert.


0

Hier ist ein anderer Ansatz, der auf dem Timing von Ereignissen basiert.

Das dragentervom untergeordneten Element ausgelöste Ereignis kann vom übergeordneten Element erfasst werden und tritt immer vor dem auf dragleave. Das Timing zwischen diesen beiden Ereignissen ist sehr kurz und kürzer als jede mögliche menschliche Mausaktion. Die Idee ist also, sich die Zeit zu merken, zu der ein Ereignis eintritt dragenter, und dragleaveEreignisse zu filtern , die "nicht zu schnell" nach ...

Dieses kurze Beispiel funktioniert unter Chrome und 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 ..

Warum Sie versuchen , nicht mit Tropfen statt Dragleave . Es hat bei mir funktioniert. Ich hoffe, dies löst Ihr Problem.

Bitte überprüfen Sie die 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
Wenn der Benutzer seine Meinung ändert und die Datei wieder aus dem Browser zieht, bleibt diese rot.
ZachB

0

Sie können eine Zeitüberschreitung mit einem transitioningFlag verwenden und das oberste Element abhören. dragenter/ dragleavevon untergeordneten Ereignissen sprudelt in den Container.

Da dragenterdas untergeordnete Element vor dragleavedem Container ausgelöst wird, setzen wir das Flag show als Übergang für 1 ms. Der dragleaveListener sucht nach dem Flag, bevor 1 ms aktiv ist.

Das Flag ist nur bei Übergängen zu untergeordneten Elementen wahr und beim Übergang zu einem übergeordneten Element (des Containers) nicht wahr.

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

Sie müssen die Zeigerereignisse für alle untergeordneten Objekte des Ziehziels entfernen.

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) {
                //
            }
        }
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.