Ziehoperationen
Zentral für die Drag-and-Drop-API sind die verschiedenen Ziehen-Ereignisse, die in einer bestimmten Reihenfolge ausgelöst werden und in einer bestimmten Weise behandelt werden sollen. Dieses Dokument beschreibt die Schritte, die während einer Drag-and-Drop-Operation auftreten, und was die Anwendung innerhalb jedes Handlers tun soll.
Auf hoher Ebene sind hier die möglichen Schritte in einer Drag-and-Drop-Operation:
- Der Benutzer startet das Ziehen an einem Quellknoten; das
dragstart
-Ereignis wird am Quellknoten ausgelöst. Innerhalb dieses Ereignisses bereitet der Quellknoten den Kontext für die Ziehoperation vor, einschließlich der Ziehdaten, Feedbackbild und erlaubten Ablageeffekten. - Der Benutzer zieht das Element herum: Jedes Mal, wenn ein neues Element betreten wird, wird das
dragenter
-Ereignis für dieses Element ausgelöst, und dasdragleave
-Ereignis wird für das vorherige Element ausgelöst. Alle paar hundert Millisekunden wird eindragover
-Ereignis für das Element ausgelöst, in dem sich das Ziehen gerade befindet, und dasdrag
-Ereignis wird am Quellknoten ausgelöst. - Das Ziehen erfolgt in ein gültiges Ziel: Das Ziel storniert sein
dragover
-Ereignis, um anzuzeigen, dass es ein gültiges Ablageziel ist. Eine Form von Ablage-Feedback zeigt dem Benutzer den erwarteten Ablageeffekt an. - Der Benutzer führt das Ablegen durch: Das
drop
-Ereignis wird für das Ablageziel ausgelöst. Innerhalb dieses Ereignisses liest der Zielknoten die Ziehdaten. - Die Ziehoperation endet: Das
dragend
-Ereignis wird am Quellknoten ausgelöst. Dieses Ereignis wird unabhängig davon ausgelöst, ob das Ablegen erfolgreich war oder nicht.
Starten eines Zugs
Das Ziehen beginnt an einem ziehbaren Element, das eine Auswahl, ein ziehbares Element (einschließlich Links, Bilder und jedes Element mit draggable="true"
), eine Datei aus dem Dateiexplorer des Betriebssystems usw. sein kann. Zuerst wird das dragstart
-Ereignis am Quellknoten ausgelöst, bei dem es sich um das ziehbare Element handelt oder, bei Auswahlen, um den Textknoten, auf dem das Ziehen begann. Wenn dieses Ereignis abgesagt wird, wird die Ziehoperation abgebrochen. Andernfalls wird auch das pointercancel
-Ereignis am Quellknoten ausgelöst.
Das dragstart
-Ereignis ist die einzige Zeit, in der Sie das dataTransfer
ändern können. Für ein benutzerdefiniertes ziehbares Element möchten Sie fast immer die Ziehdaten ändern, was im Detail in Ändern des Ziehdatenspeichers behandelt wird. Es gibt zwei weitere Dinge, die Sie ändern können: das Feedbackbild und die erlaubten Ablageeffekte.
In diesem Beispiel fügen wir einen Listener für das dragstart
-Ereignis hinzu, indem wir die addEventListener()
-Methode verwenden.
<p draggable="true">This text <strong>may</strong> be dragged.</p>
const draggableElement = document.querySelector('p[draggable="true"]');
draggableElement.addEventListener("dragstart", (event) => {
event.dataTransfer.setData("text/plain", "This text may be dragged");
});
Sie könnten auch einem höheren Vorfahren lauschen, da Drag-Ereignisse wie die meisten anderen Ereignisse nach oben blubbern. Aus diesem Grund ist es üblich, auch das Ziel des Ereignisses zu überprüfen, damit das Ziehen einer Auswahl, die innerhalb dieses Elements enthalten ist, nicht das setData
auslöst (obwohl das Auswählen von Text innerhalb des Elements schwierig ist, ist es nicht unmöglich):
draggableElement.addEventListener("dragstart", (event) => {
if (event.target === draggableElement) {
event.dataTransfer.setData("text/plain", "This text may be dragged");
}
});
Festlegen des Drag-Feedbackbildes
Wenn ein Ziehen auftritt, wird ein durchscheinendes Bild aus dem Quellknoten erzeugt, das dem Zeiger des Benutzers während des Ziehens folgt. Dieses Bild wird automatisch erstellt, sodass Sie es nicht selbst erstellen müssen. Sie können jedoch setDragImage()
verwenden, um ein benutzerdefiniertes Drag-Feedbackbild anzugeben.
draggableElement.addEventListener("dragstart", (event) => {
event.dataTransfer.setDragImage(image, xOffset, yOffset);
});
Drei Argumente sind notwendig. Das erste ist ein Verweis auf ein Bild. Dieser Verweis wird in der Regel auf ein <img>
-Element verweisen, kann aber auch auf <canvas>
oder jedes andere Element verweisen. Das Feedbackbild wird aus dem generiert, wie das Bild auf dem Bildschirm aussieht, obwohl Bilder in ihrer Originalgröße gezeichnet werden. Die zweite und dritte Argumente der setDragImage()
-Methode sind Offset-Werte, wo das Bild relativ zum Mauszeiger erscheinen soll.
Sie können auch Bilder und Canvases verwenden, die sich nicht in einem Dokument befinden. Diese Technik ist nützlich, wenn benutzerdefinierte Drag-Bilder mit dem Canvas-Element gezeichnet werden, wie im folgenden Beispiel:
draggableElement.addEventListener("dragstart", (event) => {
const canvas = document.createElement("canvas");
canvas.width = canvas.height = 50;
const ctx = canvas.getContext("2d");
ctx.lineWidth = 4;
ctx.moveTo(0, 0);
ctx.lineTo(50, 50);
ctx.moveTo(0, 50);
ctx.lineTo(50, 0);
ctx.stroke();
event.dataTransfer.setDragImage(canvas, 25, 25);
});
In diesem Beispiel machen wir ein Canvas zum Drag-Bild. Da das Canvas 50×50 Pixel groß ist, verwenden wir Offsets von der Hälfte davon (25
), damit das Bild zentriert auf dem Mauszeiger erscheint.
Über Elemente ziehen und Ablageziele angeben
Während des gesamten Verlaufs der Ziehoperation werden alle Eingabegeräteereignisse (wie Maus oder Tastatur) unterdrückt. Die gezogenen Daten können über verschiedene Elemente im Dokument oder sogar über Elemente in anderen Dokumenten bewegt werden. Jedes Mal, wenn ein neues Element betreten wird, wird ein dragenter
-Ereignis für dieses Element ausgelöst, und ein dragleave
-Ereignis wird für das vorherige Element ausgelöst.
Hinweis:
dragleave
wird immer nach dragenter
ausgelöst, sodass das Ziel konzeptionell zwischen diesen beiden Ereignissen in ein neues Element eingetreten ist, aber das vorherige noch nicht verlassen hat.
Alle paar hundert Millisekunden werden zwei Ereignisse ausgelöst: ein drag
-Ereignis am Quellknoten und ein dragover
-Ereignis am Element, in dem sich das Ziehen gerade befindet. Die meisten Bereiche einer Webseite oder Anwendung sind keine gültigen Orte zum Ablegen von Daten, sodass Elemente standardmäßig jedes Ablegen ignorieren, das darauf passiert. Das Element kann sich selbst als gültiges Ablageziel wählen, indem es das dragover
-Ereignis absagt. Wenn das Element ein bearbeitbares Textfeld ist, wie ein <textarea>
oder <input type="text">
, und der Datenspeicher ein text/plain
-Element enthält, dann ist das Element standardmäßig ein gültiges Ablageziel, ohne dragover
abzusagen.
<div id="drop-target">You can drag and then drop a draggable item here</div>
const dropElement = document.getElementById("drop-target");
dropElement.addEventListener("dragover", (event) => {
event.preventDefault();
});
Hinweis:
Die Spezifikation erfordert, dass auch das dragenter
-Ereignis für ein Ablageziel abgesagt wird, andernfalls würden die dragover
- oder dragleave
-Ereignisse nicht einmal für dieses Element ausgelöst; in der Praxis wird dies von keinem Browser implementiert, und das "aktuelle Element" ändert sich jedes Mal, wenn ein neues Element betreten wird.
Hinweis:
Die Spezifikation erfordert, dass das Abbrechen des drag
-Ereignisses das Ziehen abbricht; in der Praxis wird dies von keinem Browser implementiert. Siehe folgendes Beispiel:
Bedingte Ablageziele
In der Regel möchten Sie, dass das Ablageziel nur in bestimmten Situationen Ablegen akzeptiert (zum Beispiel nur, wenn ein Link gezogen wird). Um dies zu tun, prüfen Sie eine Bedingung und brechen Sie das Ereignis nur dann ab, wenn die Bedingung erfüllt ist. Zum Beispiel können Sie überprüfen, ob die gezogenen Daten Links enthalten:
dropElement.addEventListener("dragover", (event) => {
const isLink = event.dataTransfer.types.includes("text/uri-list");
if (isLink) {
event.preventDefault();
}
});
In diesem Beispiel verwenden wir die includes
-Methode, um zu prüfen, ob der Typ text/uri-list
in der Liste der Typen vorhanden ist. Wenn ja, werden wir das Ereignis abbrechen, damit ein Ablegen erlaubt wird. Wenn die Ziehdaten keinen Link enthalten, wird das Ereignis nicht abgebrochen, und ein Ablegen kann an dieser Position nicht stattfinden.
Ablage-Feedback
Jetzt zieht der Benutzer in ein gültiges Ablageziel. Es gibt mehrere Möglichkeiten, den Benutzer darauf hinzuweisen, dass an dieser Position ein Ablegen erlaubt ist und was möglicherweise passiert, wenn das Ablegen stattfindet. Normalerweise wird der Mauszeiger je nach Wert der dropEffect
-Eigenschaft entsprechend aktualisiert. Obwohl das genaue Erscheinungsbild von der Plattform des Benutzers abhängt, wird typischerweise ein Pluszeichen-Symbol für ein copy
angezeigt, zum Beispiel, und ein "Hier kann nicht abgelegt werden"-Symbol erscheint, wenn ein Ablegen nicht erlaubt ist. Dieses Mauszeiger-Feedback ist in vielen Fällen ausreichend.
Ablageeffekte
Beim Ablegen können mehrere Operationen durchgeführt werden:
copy
-
Die Daten sind nach dem Ablegen gleichzeitig an Quell- und Ziellocations vorhanden.
move
-
Die Daten sind nur an der Ziellocation vorhanden und werden von der Quelllocation entfernt.
link
-
Eine Form der Verknüpfung wird zwischen Quell- und Abgabelocations erstellt; es gibt nur eine Instanz der Daten an der Quelllocation.
none
-
Nichts passiert; das Ablegen ist fehlgeschlagen.
Mit den dragenter
und dragover
-Ereignissen wird die dropEffect
-Eigenschaft auf den Effekt initialisiert, den der Benutzer anfordert. Der Benutzer kann den gewünschten Effekt durch Drücken von Modifikatortasten ändern. Obwohl die genauen verwendeten Tasten je nach Plattform variieren, würden normalerweise die Tasten Shift und Control verwendet, um zwischen Kopieren, Verschieben und Verknüpfen zu wechseln. Der Mauszeiger wird sich ändern, um anzugeben, welche Operation gewünscht wird. Bei einem copy
könnte der Cursor beispielsweise mit einem Pluszeichen daneben erscheinen.
Sie können die dropEffect
-Eigenschaft während der dragenter
- oder dragover
-Ereignisse ändern, wenn beispielsweise ein bestimmtes Ablageziel nur bestimmte Operationen unterstützt. Sie können die dropEffect
-Eigenschaft ändern, um den Benutzereffekt zu überschreiben und eine bestimmte Ablageoperation durchzusetzen.
target.addEventListener("dragover", (event) => {
event.dataTransfer.dropEffect = "move";
});
In diesem Beispiel ist Verschieben der Effekt, der durchgeführt wird.
Sie können den Wert none
verwenden, um anzugeben, dass an dieser Position kein Ablegen erlaubt ist. Sie sollten dies in der Regel tun, wenn das Element vorübergehend keine Ablagen akzeptiert; wenn es nicht als Ablageziel vorgesehen ist, sollten Sie einfach das Ereignis nicht absagen.
Beachten Sie, dass das Festlegen von dropEffect
nur den gewünschten Effekt zu diesem bestimmten Zeitpunkt anzeigt; ein späteres dragover
-Dispatcher kann es ändern. Um die Wahl beizubehalten, müssen Sie sie bei jedem dragover
-Ereignis festlegen. Außerdem ist dieser Effekt nur informativ, und welche Effekte letztendlich implementiert werden, hängt sowohl von den Quell- als auch den Zielknoten ab (zum Beispiel, wenn derQuellknoten nicht modifiziert werden kann, dann kann es selbst bei einer "move"-Operation nicht möglich sein, dies durchzuführen).
Für sowohl Benutzerinteraktionen als auch programmatisches Festlegen von dropEffect
sind standardmäßig alle drei Ablageeffekte verfügbar. Das ziehbare Element kann sich darauf beschränken, nur bestimmte Effekte zuzulassen, indem es die effectAllowed
-Eigenschaft innerhalb eines dragstart
-Ereignis-Listeners festlegt.
draggableElement.addEventListener("dragstart", (event) => {
event.dataTransfer.effectAllowed = "copyLink";
});
In diesem Beispiel ist nur eine Kopier- oder Verknüpfungsoperation erlaubt, aber eine Verschiebeoperation kann weder über Skript noch über Benutzerinteraktionen ausgewählt werden.
Die Werte von effectAllowed
sind Kombinationen von dropEffect
:
Wert | Beschreibung |
---|---|
none |
Keine Operation ist erlaubt |
copy |
Nur copy |
move |
Nur move |
link |
Nur link |
copyMove |
Nur copy oder move |
copyLink |
Nur copy oder link |
linkMove |
Nur link oder move |
all |
copy , move oder link |
uninitialized |
Der Standardwert, wenn der Effekt nicht festgelegt wurde; generell äquivalent zu all , außer der Standardwert für dropEffect ist möglicherweise nicht immer copy . |
Standardmäßig wird dropEffect
basierend auf effectAllowed
in der Reihenfolge von copy
, link
, move
initialisiert und wählt den ersten aus, der erlaubt ist. Die nicht ausgewählten, aber erlaubten Effekte können ebenfalls als Standard ausgewählt werden, wenn dies zutrifft; zum Beispiel führt das Drücken der Alt-Taste unter Windows dazu, dass link
vorrangig verwendet wird. Wenn effectAllowed
uninitialized
ist und das gezogene Element ein <a>
-Link ist, ist der Standardwert für dropEffect
link
; wenn effectAllowed
uninitialized
ist und das gezogene Element eine Auswahl aus einem editierbaren Textfeld ist, ist der Standardwert für dropEffect
move
.
Benutzerdefiniertes Ablagefeedback
Für komplexere visuelle Effekte können Sie während des dragenter
-Ereignisses andere Operationen ausführen, indem Sie zum Beispiel ein Element an der Position einfügen, an der das Ablegen stattfinden wird. Dies könnte ein Einfügemarker oder ein Element sein, das das gezogene Element an seinem neuen Standort darstellt. Dazu könnten Sie ein <img>
-Element erstellen und es während des dragenter
-Ereignisses in das Dokument einfügen.
Das dragover
-Ereignis wird für das Element ausgelöst, auf das der Mauszeiger zeigt. Natürlich müssen Sie den Einfügemarker innerhalb des dragover
-Ereignis-Handlers möglicherweise umherbewegen. Sie können die clientX
- und clientY
-Eigenschaften des Ereignisses wie bei anderen Mausereignissen verwenden, um den Standort des Mauszeigers zu bestimmen.
Schließlich wird das dragleave
-Ereignis ausgelöst, wenn das Ziehen das Element verlässt. Dies ist der Zeitpunkt, zu dem Sie Einfügemarker oder Hervorhebungen entfernen sollten. Sie müssen dieses Ereignis nicht abbrechen. Das dragleave
-Ereignis wird immer ausgelöst, selbst wenn das Ziehen abgebrochen wird, sodass Sie immer sicherstellen können, dass eine Bereinigung des Einfügepunkts während dieses Ereignisses durchgeführt werden kann.
Für ein praktisches Beispiel zur Verwendung dieser Ereignisse, siehe unser Kanban-Board-Beispiel.
Ein Ablegen durchführen
Wenn der Benutzer die Maus loslässt, endet die Drag-and-Drop-Operation.
Damit das Ablegen potenziell erfolgreich ist, muss das Ablegen über einem gültigen Ablageziel stattfinden und die dropEffect
darf zur Zeit der Mausfreigabe nicht none
sein. Andernfalls wird die Ablegeoperation als fehlgeschlagen betrachtet.
Wenn das Ablegen potenziell erfolgreich ist, wird ein drop
-Ereignis für das Ablageziel ausgelöst. Sie müssen dieses Ereignis mit preventDefault()
abbrechen, damit das Ablegen tatsächlich als erfolgreich betrachtet wird. Andernfalls wird das Ablegen auch dann als erfolgreich angesehen, wenn das Ablegen darin besteht, Text (die Daten enthalten ein text/plain
-Element) in ein bearbeitbares Textfeld einzufügen. In diesem Fall wird der Text in das Feld eingefügt (entweder an der Cursorposition oder am Ende, abhängig von den Plattformkonventionen) und, falls die dropEffect
move
ist, während die Quelle eine Auswahl in einem editierbaren Bereich ist, wird die Quelle entfernt. Andernfalls wird bei allen anderen Drag-Daten und Ablagezielen das Ablegen als fehlgeschlagen betrachtet.
Während des drop
-Ereignisses sollten Sie die gewünschten Daten aus dem Ziehdatenspeicher mit DataTransfer.getData()
abrufen und an der Ablageposition einfügen. Sie können die dropEffect
-Eigenschaft verwenden, um zu bestimmen, welche Ziehoperation gewünscht wurde. Das drop
-Ereignis ist die einzige Zeit, in der Sie den Ziehdatenspeicher lesen können, abgesehen von dragstart
.
target.addEventListener("drop", (event) => {
event.preventDefault();
const data = event.dataTransfer.getData("text/plain");
target.textContent = data;
});
In diesem Beispiel wird, sobald die Daten abgerufen wurden, die Zeichenkette als Textinhalt des Ziels eingefügt. Dies hat den Effekt, den gezogenen Text dort einzufügen, wo er abgelegt wurde, vorausgesetzt, dass das Ablageziel ein Textbereich wie ein p
- oder div
-Element ist.
Die getData()
-Methode gibt eine leere Zeichenkette zurück, wenn der Datenspeicher keine Daten des angegebenen Typs enthält. Wenn Sie bedingte Ablageziele implementiert haben, sollte diese Situation nicht auftreten, da das Ablageziel nur Ablagen akzeptieren sollte, wenn die gewünschten Daten vorhanden sind.
Sie können auch andere Datentypen abrufen. Wenn die Daten ein Link sind, sollten sie den Typ text/uri-list
haben. Sie könnten dann einen Link in den Inhalt einfügen.
target.addEventListener("drop", (event) => {
event.preventDefault();
const lines = event.dataTransfer.getData("text/uri-list").split("\r\n");
lines
.filter((line) => !line.startsWith("#"))
.forEach((line) => {
const link = document.createElement("a");
link.href = line;
link.textContent = line;
target.appendChild(link);
});
});
Weitere Informationen zum Lesen von Ziehdaten finden Sie unter Arbeiten mit dem Ziehdatenspeicher.
Es ist außerdem die Verantwortung der Quelle und der Ziel-Elemente, zusammenzuarbeiten, um die dropEffect
zu implementieren – die Quelle hört auf das dragend
-Ereignis und das Ziel hört auf das drop
-Ereignis. Zum Beispiel, wenn die dropEffect
move
ist, dann muss eines dieser Elemente das gezogene Element von seinem alten Standort entfernen (normalerweise das Quell-Element selbst, da das Ziel-Element nicht unbedingt die Kontrolle über die Quelle hat).
Ein fehlgeschlagenes Ablegen
Die Drag-and-Drop-Operation wird als fehlgeschlagen betrachtet, wenn eine der folgenden Bedingungen zutrifft:
- Der Benutzer hat die Escape-Taste gedrückt
- Das Ablegen erfolgte außerhalb eines gültigen Ablageziels
- Der Ablegeeffekt war zum Zeitpunkt der Mausfreigabe
none
- Das
drop
-Ereignis wurde nicht abgebrochen und das Ablegen bestand nicht darin, Text (dertext/plain
-Daten enthält) in ein bearbeitbares Textfeld einzufügen (siehe ein Ablegen durchführen)
Für die Fälle 1 und 3, wenn der Abbruch während des Schwebens über einem gültigen Ablageziel erfolgt, erhält das Ablageziel ein dragleave
-Ereignis, als würde das Ablegen es nicht mehr stattfinden, damit es jedes Ablage-Feedback bereinigen kann. In allen Fällen wird dropEffect
für nachfolgende Ereignisse auf none
gesetzt.
Anschließend wird ein dragend
-Ereignis am Quellknoten ausgelöst. Der Browser kann eine Animation der gezogenen Auswahl anzeigen, die zurück zur Quelle der Drag-and-Drop-Operation geht.
Beenden des Zugs
Sobald das Ziehen abgeschlossen ist, wird ein dragend
-Ereignis an der Quelle des Ziehens ausgelöst (dasselbe Element, das das dragstart
-Ereignis empfangen hat). Dieses Ereignis wird unabhängig davon ausgelöst, ob das Ziehen erfolgreich war oder nicht.
Hat die dropEffect
-Eigenschaft den Wert none
während eines dragend
, wurde das Ziehen abgebrochen. Andernfalls gibt der Effekt an, welche Operation durchgeführt wurde. Die Quelle kann diese Informationen nach einer move
-Operation verwenden, um das gezogene Element von der alten Position zu entfernen.
Ein Ablegen kann im selben Fenster oder über eine andere Anwendung erfolgen. Das dragend
-Ereignis wird immer ausgelöst, unabhängig davon, wo es stattfindet. Die screenX
- und screenY
-Eigenschaften des Ereignisses werden auf die Bildschirmkoordinaten gesetzt, an denen das Ablegen stattgefunden hat.
Nachdem das dragend
-Ereignis die Propagation beendet hat, ist die Drag-and-Drop-Operation abgeschlossen.