Bestimmen Sie, was aus Dragenter- und Dragover-Ereignissen gezogen wird

73

Ich versuche, die ziehbare HTML5-API zu verwenden (obwohl mir klar ist, dass sie Probleme hat ). Bisher ist der einzige Showstopper, dem ich begegnet bin, dass ich nicht herausfinden kann, was gezogen wird, wenn ein dragoveroder ein dragenterEreignis ausgelöst wird:

el.addEventListener('dragenter', function(e) {
  // what is the draggable element?
});

Mir ist klar, dass ich davon ausgehen kann, dass es das letzte Element ist, das ein dragstartEreignis auslöst, aber ... Multitouch. Ich habe auch versucht, e.dataTransfer.setDatavon der dragstartzu verwenden, um eine eindeutige Kennung anzuhängen, aber anscheinend ist auf diese Daten von / : nicht zugegriffen werden.dragoverdragenter

Diese Daten sind nur verfügbar, wenn während des Drop-Ereignisses ein Drop auftritt.

Also irgendwelche Ideen?

Update: Zum jetzigen Zeitpunkt scheint HTML5-Drag-and-Drop in keinem größeren mobilen Browser implementiert zu sein, weshalb Multitouch in der Praxis nicht erwähnt wird. Ich möchte jedoch eine Lösung, die garantiert für jede Implementierung der Spezifikation funktioniert, was nicht zu verhindern scheint, dass mehrere Elemente gleichzeitig gezogen werden.

Ich habe unten eine funktionierende Lösung veröffentlicht , aber es ist ein hässlicher Hack. Ich hoffe immer noch auf eine bessere Antwort.

Trevor Burnham
quelle
1
Normalerweise sollten Sie in der Lage sein, die Eigenschaft "this" innerhalb der Funktion zu verwenden, die mit dem Objekt verknüpft ist, das das Ereignis ausgelöst hat.
lgomezma
2
@lgomezma Nein, thisin einem dragenter/ dragoverevent-Handler wird auf das Element verwiesen, über das gezogen wird, nicht auf das Element, das gezogen wird. Es ist gleich e.target. Beispiel: jsfiddle.net/TrevorBurnham/jWCSB
Trevor Burnham
Ich glaube nicht, dass Microsoft Multitouch im Sinn hatte, als sie ursprünglich Drag & Drop für IE 5 entworfen und implementiert haben .
Jeffery bis
@JefferyTo ich bin mir dessen bewusst, aber jetzt, da ihr Design als Standard kodifiziert wurde, möchte ich zukunftssicher sein, indem ich gegen die WHATWG-Spezifikation arbeite , nicht nur gegen vorhandene Implementierungen.
Trevor Burnham
Dieser Fehler wurde gegen die Spezifikation abgelegt
w3.org/Bugs/Public/show_bug.cgi?id=23486

Antworten:

69

Ich wollte hier eine sehr klare Antwort hinzufügen, damit es für jeden offensichtlich ist, der hier vorbeigeht. Es wurde mehrmals in anderen Antworten gesagt, aber hier ist es, so klar ich es machen kann:

dragover HAT NICHT DIE RECHTE, um die Daten im Drag-Ereignis anzuzeigen.

Diese Informationen sind nur während DRAG_START und DRAG_END (Drop) verfügbar.

Das Problem ist, dass es überhaupt nicht offensichtlich und verrückt ist, bis Sie zufällig tief genug über die Spezifikation oder Orte wie hier lesen.

ARBEITSUMGEBUNG:

Als mögliche Umgehung habe ich dem DataTransfer-Objekt spezielle Schlüssel hinzugefügt und diese getestet. Um beispielsweise die Effizienz zu verbessern, wollte ich beim Starten des Ziehens einige Regeln zum Löschen von Zielen nachschlagen, anstatt jedes Mal, wenn ein "Ziehen" auftrat. Zu diesem Zweck habe ich dem dataTransfer-Objekt Schlüssel hinzugefügt, die jede Regel identifizieren, und diese mit "enthält" getestet.

ev.originalEvent.dataTransfer.types.includes("allow_drop_in_non_folders")

Und solche Sachen. Um klar zu sein, dass "Includes" kein Wundermittel ist und selbst zu einem Leistungsproblem werden kann. Achten Sie darauf, Ihre Verwendung und Szenarien zu verstehen.

Bladnman
quelle
1
Dies ist ein guter Rat, wenn Sie IE / Edge nicht unterstützen. IE / Edge erlaubt nur zwei Werte als Typ: "Text" oder "URL".
Wawka
Die Problemumgehung könnte sogar mehr Daten mit JSON.stringifyund JSON.parse( poc ) übergeben. Sehr unelegant
Ulysse BN
3
Ich mag klare Antworten, die mir ins Gesicht schreien. Leicht zu merken: D
Wilt
24

Die kurze Antwort auf meine Frage erweist sich als: Nein . Die WHATWG spec nicht einen Verweis auf das Element in die Länge gezogen wird schaffen , in den (die „Quellenknoten“ in der Spezifikation genannt) dragenter, dragoveroder dragleaveVeranstaltungen.

Warum nicht? Zwei Gründe:

Erstens basiert die WHATWG-Spezifikation , wie Jeffery in seinem Kommentar hervorhebt, auf der Implementierung von Drag-and-Drop durch IE5 +, die älter war als Multitouch-Geräte. (Zum jetzigen Zeitpunkt implementiert kein großer Multitouch-Browser HTML-Drag & Drop.) In einem "Single-Touch" -Kontext ist es einfach, einen globalen Verweis auf das aktuell gezogene Element zu speichern dragstart.

Zweitens können Sie mit HTML-Drag & Drop Elemente über mehrere Dokumente ziehen. Das ist genial, aber es bedeutet auch , dass ein Verweis auf das Elemente Befinden in jedem geschleppt dragenter, dragoveroder dragleaveEreignisse würde keinen Sinn machen; Sie können nicht auf ein Element in einem anderen Dokument verweisen. Es ist eine Stärke der API, dass diese Ereignisse auf dieselbe Weise funktionieren, unabhängig davon, ob der Drag aus demselben oder einem anderen Dokument stammt.

Die Unfähigkeit, serialisierte Informationen für alle Drag-Ereignisse bereitzustellen, außer für dataTransfer.types(wie in meiner Antwort auf die Arbeitslösung beschrieben ), ist eine offensichtliche Lücke in der API. Ich habe der WHATWG einen Vorschlag für öffentliche Daten in Drag-Events vorgelegt , und ich hoffe, Sie werden Ihre Unterstützung zum Ausdruck bringen.

Trevor Burnham
quelle
Ich hatte das gleiche Problem und kam zu dem Schluss, dass es am besten ist, einen benutzerdefinierten MIME-Typ in die Datenübertragung einzubeziehen. Da der Hauptzweck der von Ihnen bereitgestellten Drag-Over-Funktion darin besteht, das Ereignis zu stoppen, sind meines Erachtens einige Informationen erforderlich, auf denen dies basiert. Für viele Zwecke scheint es in Ordnung zu sein, sagen zu können, dass "das Ziehen Anwendung / myapp enthält und ich damit umgehen kann, also das Ereignis abbrechen".
Woody
1
Ich habe Ihren Vorschlag Link gesehen, aber ich weiß wirklich nicht, wo ich Antworten darauf lesen oder sogar meine Unterstützung ausdrücken kann
Ulysse BN
11

A (sehr unelegant) Lösung ist , einen Selektor als speichern Typ von Daten in dem dataTransferObjekt. Hier ist ein Beispiel: http://jsfiddle.net/TrevorBurnham/eKHap/

Die aktiven Zeilen hier sind

e.dataTransfer.setData('text/html', 'foo');
e.dataTransfer.setData('draggable', '');

Dann , in den dragoverund dragenterEreignissen e.dataTransfer.typesenthält , die Zeichenfolge 'draggable', die die Identifikation ist erforderlich , um zu bestimmen , welches Element gezogen wird. (Beachten Sie, dass in Browsern anscheinend Daten für einen erkannten MIME-Typ wie festgelegt werden text/htmlmüssen, damit dies funktioniert. Getestet in Chrome und Firefox.)

Es ist ein hässlicher, hässlicher Hack, und wenn mir jemand eine bessere Lösung geben kann, werde ich ihm gerne das Kopfgeld gewähren.

Update: Eine Einschränkung, die hinzugefügt werden sollte, ist, dass die Spezifikation nicht nur unelegant ist, sondern auch angibt, dass alle Datentypen in ASCII in Kleinbuchstaben konvertiert werden. Seien Sie also gewarnt, dass Selektoren mit Großbuchstaben oder Unicode nicht funktionieren. Jefferys Lösung umgeht dieses Problem.

Trevor Burnham
quelle
Ja,
RRR
6

Angesichts der aktuellen Spezifikation glaube ich nicht, dass es eine Lösung gibt, die kein "Hack" ist. Die Petition an die WHATWG ist eine Möglichkeit, dies zu beheben :-)

Erweiterung der "(sehr uneleganten) Lösung" ( Demo ):

  • Erstellen Sie einen globalen Hash aller Elemente, die gerade gezogen werden:

    var dragging = {};
    
  • dragstartWeisen Sie im Handler dem Element eine Drag-ID zu (falls noch keine vorhanden ist), fügen Sie das Element dem globalen Hash hinzu und fügen Sie dann die Drag-ID als Datentyp hinzu:

    var dragId = this.dragId;
    
    if (!dragId) {
        dragId = this.dragId = (Math.random() + '').replace(/\D/g, '');
    }
    
    dragging[dragId] = this;
    
    e.dataTransfer.setData('text/html', dragId);
    e.dataTransfer.setData('dnd/' + dragId, dragId);
    
  • dragenterSuchen Sie im Handler die Drag-ID unter den Datentypen und rufen Sie das ursprüngliche Element aus dem globalen Hash ab:

    var types = e.dataTransfer.types, l = types.length, i = 0, match, el;
    
    for ( ; i < l; i++) {
        match = /^dnd\/(\w+)$/.exec(types[i].toLowerCase());
    
        if (match) {
            el = dragging[match[1]];
    
            // do something with el
        }
    }
    

Wenn Sie den draggingHash für Ihren eigenen Code privat halten, kann der Code eines Drittanbieters das ursprüngliche Element nicht finden, obwohl er auf die Drag-ID zugreifen kann.

Dies setzt voraus, dass jedes Element nur einmal gezogen werden kann. Mit Multi-Touch wäre es wahrscheinlich möglich, dasselbe Element mehrmals mit verschiedenen Fingern zu ziehen ...


Update: Um mehrere Drags für dasselbe Element zu ermöglichen, können wir eine Drag-Anzahl in den globalen Hash aufnehmen: http://jsfiddle.net/jefferyto/eKHap/2/

Jeffery To
quelle
Ooph, mir ist nicht in den Sinn gekommen, dass dasselbe Element möglicherweise mehrmals gleichzeitig gezogen werden könnte. Die Spezifikation scheint dies nicht auszuschließen.
Trevor Burnham
@TrevorBurnham Ich habe meine Antwort aktualisiert, um mehrere Drags für dasselbe Element zuzulassen, aber ehrlich gesagt habe ich keine Ahnung, wie sich die Spezifikation ändern wird, um Multi-Touch zu ermöglichen.
Jeffery bis
4

Um zu überprüfen, ob es sich um eine Datei handelt, verwenden Sie:

e.originalEvent.dataTransfer.items[0].kind

Um den Typ zu überprüfen, verwenden Sie:

e.originalEvent.dataTransfer.items[0].type

dh ich möchte nur eine einzige Datei jpg, png, gif, bmp zulassen

var items = e.originalEvent.dataTransfer.items;
var allowedTypes = ["image/jpg", "image/png", "image/gif", "image/bmp"];
if (items.length > 1 || items["0"].kind != "file" || items["0"].type == "" || allowedTypes.indexOf(items["0"].type) == -1) {
    //Type not allowed
}

Referenz: https://developer.mozilla.org/it/docs/Web/API/DataTransferItem

user2272143
quelle
Ich glaube nicht, dass originalEventes in den meisten Fällen bevölkert ist, auch nicht für Chrome.
Windmaomao
@windmaomao Ich habe es mit Chrome, Edge und Firefox getestet und es funktioniert für alle.
user2272143
3

Sie können bestimmen, was beim Starten des Ziehens gezogen wird, und dies in einer Variablen speichern, die beim Auslösen der Dragover- / Dragenter-Ereignisse verwendet wird:

var draggedElement = null;

function drag(event) {
    draggedElement = event.srcElement || event.target;
};

function dragEnter(event) {
    // use the dragged element here...
};
tjscience
quelle
Ich habe dies in meiner Frage erwähnt. Der Ansatz ist auf Multitouch-Geräten unhaltbar, bei denen mehrere Elemente gleichzeitig gezogen werden können.
Trevor Burnham
3

dragKopieren Sie in diesem Fall event.xund event.yin ein Objekt und legen Sie es als Wert einer expando-Eigenschaft für das gezogene Element fest.

function drag(e) {
    this.draggingAt = { x: e.x, y: e.y };
}

In den dragenterund dragleaveEreignisse das Element , dessen expando Eigenschaftswert finden entspricht der event.xund event.ydes aktuellen Ereignisses.

function dragEnter(e) {
    var draggedElement = dragging.filter(function(el) {
        return el.draggingAt.x == e.x && el.draggingAt.y == e.y;
    })[0];
}

Um die Anzahl der Elemente zu verringern, die Sie anzeigen müssen, können Sie die Elemente verfolgen, indem Sie sie einem Array hinzufügen oder eine Klasse im dragstartEreignis zuweisen und diese im dragendEreignis rückgängig machen .

var dragging = [];
function dragStart(e) {
    e.dataTransfer.setData('text/html', '');
    dragging.push(this);
}
function dragEnd(e) {
    dragging.splice(dragging.indexOf(this), 1);
}

http://jsfiddle.net/gilly3/4bVhL/

Theoretisch sollte dies funktionieren. Ich weiß jedoch nicht, wie ich das Ziehen für ein Touch-Gerät aktivieren soll, daher konnte ich es nicht testen. Dieser Link ist mobil formatiert, aber durch Berühren und Schieben wurde das Ziehen auf meinem Android nicht gestartet. http://fiddle.jshell.net/gilly3/4bVhL/1/show/

Bearbeiten: Nach dem, was ich gelesen habe, sieht es nicht so aus, als würde HTML5 Draggable auf allen Touch-Geräten unterstützt. Können Sie Draggable auf Touch-Geräten verwenden? Wenn nicht, wäre Multi-Touch kein Problem, und Sie können einfach das gezogene Element in einer Variablen speichern.

gilly3
quelle
@ Trevor - Wirklich? Meine jsfiddle funktioniert für mich in Chrome.
Gilly3
@ gilly3 Wie funktioniert deine Methode? Ich habe kein Multitouch-Gerät verwendet, aber ich gehe davon aus, dass Sie mehrere Objekte gleichzeitig ziehen können. Ist das Element, das zuletzt das Drag-Ereignis aufgerufen hat, garantiert das Element, das das Drag-Enter-Ereignis zuerst aufruft? (Mein Wortlaut ist nicht der klarste, aber hoffentlich wissen Sie, was ich meine)
Jules
@Jules - Ich würde erwarten, dass Drag und Dragenter in einer Multi-Touch-Umgebung (die HTML5-Draggable unterstützt) unmittelbar hintereinander für dasselbe Element aufgerufen werden, und ich wäre überrascht, wenn dies nicht der Fall wäre. Aber ich wäre mehr überrascht, wenn dies garantiert wäre (dh durch die Spezifikation so definiert). Meine Methode behält ein Array von Elementen bei, die gezogen werden, und sucht nach dem Element, das sich an derselben Position wie das aktuelle Ereignis befindet. Ich weiß, an welcher Position sich jedes gezogene Element befindet, da ich eine benutzerdefinierte Eigenschaft für das Element im dragEreignishandler aktualisiere .
Gilly3
@ gilly3 Ich verstehe was du meinst. Wenn Sie darüber nachdenken, wird ein Dragenter nur direkt nach dem Ziehen überprüft, sodass es keinen Grund gibt, den Dragenter nicht sofort anzurufen, bevor Sie nach anderen Zügen suchen.
Jules
@ gilly3 Die Spezifikation garantiert, dass dragimmer vorher ausgelöst wird dragenter(die Tatsache, dass es für mich nicht ausgelöst wurde, war auf einen Fehler in einer ganz bestimmten Version von Chrome zurückzuführen). Dies garantiert jedoch nicht xund yhat konsistente Werte. Insgesamt ist dies eine kluge Antwort und ich gebe ihr eine +1, aber ich glaube nicht, dass ich sie als Antwort akzeptieren kann.
Trevor Burnham
0

Nach dem, was ich auf MDN gelesen habe, ist das, was Sie tun, richtig.

MDN listet einige empfohlene Drag-Typen auf , z. B. text / html. Wenn jedoch keine geeignet sind, speichern Sie die ID einfach als Text mit dem Typ 'text / html' oder erstellen Sie einen eigenen Typ, z. B. 'application / node-id'.

Jules
quelle
Wie ich in der Frage sage: Sie können versuchen, die ID als text/htmlDaten zu speichern , aber Sie können aufgrund des Sicherheitsmodells erst nach dem Löschen auf diese Daten zugreifen. Siehe die Spezifikation , insb. "Sicherheitsmodus."
Trevor Burnham
Entschuldigung, das habe ich nicht verstanden! Ist dies ein visueller Hinweis, wenn es in Ordnung ist, fallen zu lassen? Könnten Sie zumindest versuchen, im Dragenter-Ereignis zu fangen? Wenn es um das Löschen geht, haben Sie dann alle Informationen zur Verfügung und können das Löschen dann abbrechen, wenn es nicht anwendbar ist.
Jules
-1

Ich denke, Sie können es erhalten, indem Sie e.relatedTarget See anrufen : http://help.dottoro.com/ljogqtqm.php

OK, ich habe es versucht e.target.previousElementSiblingund es funktioniert, irgendwie ... http://jsfiddle.net/Gz8Qw/4/ Ich denke, es hängt auf, weil das Ereignis zweimal ausgelöst wird. Einmal für den div und einmal für den Textknoten (wenn er für den Textknoten ausgelöst wird, ist dies der Fall undefined). Ich bin mir nicht sicher, ob das dich dahin bringt, wo du sein willst oder nicht ...

solidau
quelle
Nee. Probieren Sie es aus . In Chrome e.relatedTargetist null. In Firefox ist es etwas seltsam XrayWrapper.
Trevor Burnham
Ich habe diese Ergebnisse nicht erhalten (für mich gab es #dragTarget zurück), aber mir wurde klar, dass dies immer noch nicht das ist, wonach Sie gesucht haben.
Solidau
Änderung meines vorherigen Kommentars: XrayWrapperDies ist eine Eigenart der Firefox-Entwicklungstools, die immer dann angezeigt werden, wenn Sie console.logein DOM-Element verwenden. Auf jeden Fall war das, worüber es gewickelt wurde, das umhüllte Ding, nicht das Schleppbare.
Trevor Burnham
1
e.target.previousElementSiblingfunktioniert nur, weil das draggabledas Element vor dem Drag-Ziel ist ...
Trevor Burnham
ahh, ich verstehe, ich dachte 'vorher' in einem anderen Kontext. Ich sehe jetzt, dass es buchstäblich das vorherige Element im DOM ist. Ich bin froh, dass Sie darauf hinweisen (anstatt nur leer zu lassen) und mir dabei helfen, auch zu verstehen.
Solidau