Drag-Operationen
Im Zentrum der Drag and Drop API stehen die verschiedenen Drag-Ereignisse, die in einer bestimmten Reihenfolge ausgelöst werden und auf eine bestimmte Weise behandelt werden sollen. Dieses Dokument beschreibt die Schritte, die bei einem Drag-and-Drop-Vorgang auftreten, und was die Anwendung in jedem Ereignis-Handler tun soll.
Auf hoher Ebene sind hier die möglichen Schritte bei einem Drag-and-Drop-Vorgang:
- Der Benutzer startet das Ziehen an einem Quellknoten; das
dragstart-Ereignis wird auf dem Quellknoten ausgelöst. Innerhalb dieses Ereignisses bereitet der Quellknoten den Kontext für die Drag-Operation vor, einschließlich der Drag-Daten, Feedback-Bild und erlaubten Drop-Effekte. - Der Benutzer zieht das Element herum: Jedes Mal, wenn ein neues Element betreten wird, wird das
dragenter-Ereignis auf diesem Element ausgelöst und dasdragleave-Ereignis auf dem vorherigen Element. Dasdragover-Ereignis wird wiederholt auf dem Element ausgelöst, in dem sich der Drag derzeit befindet, und dasdrag-Ereignis wird wiederholt auf dem Quellknoten ausgelöst. - Der Drag gelangt in ein gültiges Ziel: Das Ziel storniert sein
dragover-Ereignis, um anzuzeigen, dass es ein gültiges Ziel ist. Eine Form von Drop-Feedback zeigt dem Benutzer den erwarteten Drop-Effekt. - Der Benutzer führt den Drop aus: Das
drop-Ereignis wird auf dem Ziel ausgelöst. Innerhalb dieses Ereignisses liest der Zielknoten die Drag-Daten. - Die Drag-Operation endet: Das
dragend-Ereignis wird auf dem Quellknoten ausgelöst. Dieses Ereignis wird ausgelöst, unabhängig davon, ob der Drop erfolgreich war oder nicht.
Starten eines Drags
Der Drag 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 Datei-Explorer des Betriebssystems usw. sein kann. Zuerst wird das dragstart-Ereignis auf dem Quellknoten ausgelöst, bei dem es sich um das ziehbare Element oder, bei Auswahlen, um den Textknoten handelt, bei dem das Ziehen begann. Wenn dieses Ereignis abgebrochen wird, wird die Drag-Operation abgebrochen. Andernfalls wird das pointercancel-Ereignis ebenfalls auf dem Quellknoten ausgelöst.
Das dragstart-Ereignis ist die einzige Zeit, in der Sie den dataTransfer ändern können. Für ein benutzerdefiniertes ziehbares Element möchten Sie fast immer die Drag-Daten ändern, was im Detail unter Ändern des Drag-Daten-Stores behandelt wird. Es gibt zwei weitere Dinge, die Sie ändern können: das Feedback-Bild und die erlaubten Drop-Effekte.
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 auf einen höheren Vorfahren hören, 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-Feedback-Bildes
Wenn ein Drag auftritt, wird ein durchsichtiges Bild aus dem Quellknoten generiert und folgt dem Zeiger des Benutzers während des Drags. Dieses Bild wird automatisch erstellt, sodass Sie es nicht selbst erstellen müssen. Sie können jedoch setDragImage() verwenden, um ein benutzerdefiniertes Drag-Feedback-Bild anzugeben.
draggableElement.addEventListener("dragstart", (event) => {
event.dataTransfer.setDragImage(image, xOffset, yOffset);
});
Drei Argumente sind erforderlich. Das erste ist ein Verweis auf ein Bild. Dieser Verweis bezieht sich typischerweise auf ein <img>-Element, kann aber auch auf ein <canvas> oder ein anderes Element verweisen. Das Feedback-Bild wird aus dem, wie das Bild auf dem Bildschirm aussieht, generiert, obwohl Bilder in ihrer Originalgröße gezeichnet werden. Die zweiten und dritten Argumente der setDragImage()-Methode sind Offsets, wo das Bild relativ zum Mauszeiger erscheinen soll.
Sie können auch Bilder und Leinwände 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), sodass das Bild zentriert auf dem Mauszeiger erscheint.
Über Elemente ziehen und Ziele festlegen
Während der gesamten Drag-Operation werden alle Geräteeingabeereignisse (wie Maus oder Tastatur) unterdrückt. Die gezogenen Daten können über verschiedene Elemente im Dokument oder sogar Elemente in anderen Dokumenten bewegt werden. Jedes Mal, wenn ein neues Element betreten wird, wird ein dragenter-Ereignis auf diesem Element ausgelöst und ein dragleave-Ereignis auf dem vorherigen Element.
Hinweis:
dragleave wird immer nach dragenter ausgelöst, sodass konzeptionell zwischen diesen beiden Ereignissen das Ziel ein neues Element betreten hat, aber das vorherige noch nicht verlassen hat.
Während des Drags wird das drag-Ereignis wiederholt auf dem Quellknoten ausgelöst, und das dragover-Ereignis wird wiederholt auf dem Element ausgelöst, in dem sich der Drag derzeit befindet. Die meisten Bereiche einer Webseite oder Anwendung sind keine gültigen Orte zum Ablegen von Daten, daher ignorieren Elemente standardmäßig jeden Drop, der auf sie erfolgt. Das Element kann sich als gültiges Ziel deklarieren, indem es das dragover-Ereignis abbricht. Wenn das Element ein bearbeitbares Textfeld ist, wie z.B. ein <textarea> oder <input type="text">, und der Datenstore ein text/plain-Element enthält, ist das Element standardmäßig ohne Abbrechen von dragover ein gültiges Ziel.
<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 das dragenter-Ereignis für ein Ziel ebenfalls abgebrochen wird, andernfalls werden die dragover- oder dragleave-Ereignisse auf diesem Element gar nicht erst ausgelöst; in der Praxis implementiert kein Browser dies, 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 implementiert kein Browser dies. Siehe das Beispiel unten:
Bedingte Ziele
Sie möchten in der Regel nur, dass das Ziel Drops in bestimmten Situationen akzeptiert (z.B. nur, wenn ein Link gezogen wird). Um dies zu tun, überprüfen Sie eine Bedingung und brechen das Ereignis nur 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 überprüfen, ob der Typ text/uri-list in der Liste der Typen vorhanden ist. Wenn er vorhanden ist, brechen wir das Ereignis ab, damit ein Drop erlaubt ist. Wenn die Drag-Daten keinen Link enthalten, wird das Ereignis nicht abgebrochen, und ein Drop kann an dieser Stelle nicht erfolgen.
Drop-Feedback
Jetzt zieht der Benutzer in ein gültiges Ziel. Es gibt mehrere Möglichkeiten, dem Benutzer anzuzeigen, dass ein Drop an dieser Stelle erlaubt ist und was passieren könnte, wenn der Drop durchgeführt wird. Normalerweise wird der Mauszeiger je nach Wert der dropEffect-Eigenschaft wie notwendig aktualisiert. Obwohl das genaue Aussehen von der Plattform des Benutzers abhängt, wird typischerweise ein Pluszeichen-Symbol für ein copy angezeigt und ein "Kann hier nicht abgelegt werden"-Symbol, wenn ein Drop nicht erlaubt ist. Dieses Mauszeiger-Feedback ist in vielen Fällen ausreichend.
Drop-Effekte
Beim Ablegen gibt es mehrere Operationen, die durchgeführt werden können:
copy-
Die Daten sind nach dem Ablegen gleichzeitig am Quell- und Zielort vorhanden.
move-
Die Daten sind nur noch am Zielort vorhanden und werden vom Quellort entfernt.
link-
Eine Form von Verlinkung wird zwischen dem Quell- und Zielort erstellt; es gibt nur eine Instanz der Daten am Quellort.
none-
Nichts passiert; der Drop ist fehlgeschlagen.
Mit den dragenter- und dragover-Ereignissen wird die dropEffect-Eigenschaft auf den vom Benutzer gewünschten Effekt initialisiert. Der Benutzer kann den gewünschten Effekt durch Drücken von Modifikator-Tasten ändern. Obwohl sich die genauen Tasten je nach Plattform ändern, würden typischerweise die Tasten Shift und Control verwendet, um zwischen Kopieren, Verschieben und Verlinken zu wechseln. Der Mauszeiger ändert sich, um anzuzeigen, welche Operation gewünscht ist. Zum Beispiel könnte für ein copy der Cursor mit einem Pluszeichen daneben erscheinen.
Sie können die dropEffect-Eigenschaft während der dragenter- oder dragover-Ereignisse ändern, wenn ein bestimmtes Ziel beispielsweise nur bestimmte Operationen unterstützt. Sie können die dropEffect-Eigenschaft ändern, um den Benutzereffekt zu überschreiben und eine spezifische Drop-Operation zu erzwingen.
target.addEventListener("dragover", (event) => {
event.dataTransfer.dropEffect = "move";
});
In diesem Beispiel wird die Operation move durchgeführt.
Sie können den Wert none verwenden, um anzuzeigen, dass an dieser Stelle kein Drop erlaubt ist. Diese Option sollten Sie normalerweise verwenden, wenn das Element nur vorübergehend keine Drops akzeptiert; wenn es nicht als Drop-Ziel gedacht ist, sollten Sie das Ereignis einfach nicht abbrechen.
Beachten Sie, dass das Setzen von dropEffect nur den gewünschten Effekt in diesem speziellen Augenblick anzeigt; eine spätere dragover-Dispatch kann dies ändern. Um die Auswahl beizubehalten, müssen Sie es in jedem dragover-Ereignis festlegen. Außerdem ist dieser Effekt nur informativ, und welche Effekte letztendlich umgesetzt werden, hängt sowohl von den Quell- als auch den Zielknoten ab (z.B. wenn der Quellknoten nicht geändert werden kann, dann kann selbst bei einer angeforderten "move"-Operation dies nicht möglich sein).
Sowohl bei Benutzeraktionen als auch beim programmgesteuerten Setzen von dropEffect sind standardmäßig alle drei Drop-Effekte 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 Verlinkungsoperation erlaubt, aber eine Verschiebungsoperation kann weder über ein Skript noch über Benutzeraktionen 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; im Allgemeinen gleichwertig zu all, außer dass der Standard-dropEffect nicht immer copy sein muss. |
Standardmäßig wird der dropEffect basierend auf effectAllowed initialisiert, in der Reihenfolge von copy, link, move, wobei der erste erlaubte Effekt ausgewählt wird. Die nicht ausgewählten, aber erlaubten Effekte können ebenfalls als Standard ausgewählt werden, wenn es angemessen ist; zum Beispiel auf Windows wird, wenn die Alt-Taste gedrückt wird, link als Priorität verwendet. Wenn effectAllowed uninitialized ist und das gezogene Element ein <a>-Link ist, ist der Standard-dropEffect link; wenn effectAllowed uninitialized ist und das gezogene Element eine Auswahl aus einem bearbeitbaren Textfeld ist, ist der Standard-dropEffect move.
Benutzerdefiniertes Drop-Feedback
Für komplexere visuelle Effekte können Sie während des dragenter-Ereignisses andere Operationen ausführen, zum Beispiel indem Sie ein Element an der Stelle einfügen, an der der Drop erfolgen wird. Dies könnte ein Einfügemarker oder ein Element sein, das das gezogene Element an seinem neuen Ort repräsentiert. Dazu könnten Sie ein <img>-Element erstellen und es während des dragenter-Ereignisses in das Dokument einfügen.
Das dragover-Ereignis wird am Element ausgelöst, auf das die Maus zeigt. Natürlich müssen Sie den Einfügemarker möglicherweise auch im dragover-Ereignis-Handler bewegen. Sie können die clientX und clientY-Eigenschaften des Ereignisses wie bei anderen Mausereignissen verwenden, um die Position des Mauszeigers zu bestimmen.
Abschließend wird das dragleave-Ereignis bei einem Element ausgelöst, wenn der Drag das Element verlässt. Dies ist der Zeitpunkt, an dem Sie alle Einfügemarkierungen oder Hervorhebungen entfernen sollten. Sie müssen dieses Ereignis nicht stornieren. Das dragleave-Ereignis wird immer ausgelöst, selbst wenn der Drag abgebrochen wird, sodass Sie immer sicherstellen können, dass die Reinigung 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.
Ausführen eines Drops
Wenn der Benutzer die Maus loslässt, endet die Drag-and-Drop-Operation.
Damit der Drop möglicherweise erfolgreich ist, muss der Drop über einem gültigen Ziel erfolgen, und der dropEffect darf zum Zeitpunkt des Loslassens der Maus nicht none sein. Andernfalls wird die Drop-Operation als fehlgeschlagen betrachtet.
Wenn der Drop möglicherweise erfolgreich ist, wird ein drop-Ereignis auf dem Zielknoten ausgelöst. Sie müssen dieses Ereignis mit preventDefault() abbrechen, damit der Drop als tatsächlich erfolgreich gilt. Andernfalls wird der Drop auch dann als erfolgreich betrachtet, wenn der Drop das Ablegen von Text (die Daten enthalten ein text/plain-Element) in ein bearbeitbares Textfeld war. In diesem Fall wird der Text in das Feld eingefügt (entweder an der Cursorposition oder am Ende, je nach Plattformkonventionen) und, wenn der dropEffect move ist, während die Quelle eine Auswahl innerhalb eines bearbeitbaren Bereichs war, wird die Quelle entfernt. Andernfalls wird für alle anderen Drag-Daten und Ziele der Drop als fehlgeschlagen betrachtet.
Während des drop-Ereignisses sollten Sie die gewünschten Daten aus dem Drag-Daten-Store mit DataTransfer.getData() abrufen und an der Drop-Position einfügen. Sie können die dropEffect-Eigenschaft verwenden, um festzustellen, welche Drag-Operation gewünscht war. Das drop-Ereignis ist die einzige Zeit, in der Sie den Drag-Daten-Store 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 fügen wir, sobald die Daten abgerufen wurden, die Zeichenkette als Textinhalt des Ziels ein. Dies hat den Effekt, dass der gezogene Text dort eingefügt wird, wo er abgelegt wurde, vorausgesetzt, dass das Ziel ein Textbereich wie ein p- oder div-Element ist.
Die getData()-Methode gibt eine leere Zeichenkette zurück, wenn der Daten-Store keine Daten des angegebenen Typs enthält. Wenn Sie bedingte Ziele implementiert haben, sollte diese Situation nicht auftreten, da das Ziel nur Drops 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 Drag-Daten finden Sie unter Arbeiten mit dem Drag-Daten-Store.
Es liegt auch in der Verantwortung der Quell- und Zielelemente, zusammenzuarbeiten, um den dropEffect zu implementieren — die Quelle hört auf das dragend-Ereignis und das Ziel hört auf das drop-Ereignis. Wenn der dropEffect zum Beispiel move ist, muss eines dieser Elemente das gezogene Element aus seiner alten Position entfernen (normalerweise das Quell-Element selbst, da das Ziel-Element nicht unbedingt Kontrolle über die Quelle hat).
Ein fehlgeschlagener Drop
Der Drag-and-Drop-Vorgang wird als fehlgeschlagen betrachtet, wenn eine der folgenden Bedingungen erfüllt ist:
- Der Benutzer drückt die Escape-Taste
- Der Drop erfolgt außerhalb eines gültigen Ziels
- Der Drop-Effekt war
nonezum Zeitpunkt des Loslassens der Maus - Das
drop-Ereignis wurde nicht abgebrochen und der Drop war nicht das Ablegen von Text (mittext/plain-Daten) in ein bearbeitbares Textfeld (siehe Ausführen eines Drops)
In den Fällen 1 und 3, wenn das Abbrechen über einem gültigen Ziel erfolgt, empfängt das Ziel ein dragleave-Ereignis, als ob der Drop keine Auswirkungen mehr auf es hätte, damit es jegliches Drop-Feedback bereinigen kann. In allen Fällen wird der dropEffect für nachfolgende Ereignisse auf none gesetzt.
Anschließend wird ein dragend-Ereignis an der Quelle ausgelöst. Der Browser kann eine Animation der gezogenen Auswahl anzeigen, die zur Quelle der Drag-and-Drop-Operation zurückkehrt.
Beenden des Drags
Sobald der Drag abgeschlossen ist, wird ein dragend-Ereignis an der Quelle des Drags (dem gleichen Element, das das dragstart-Ereignis erhielt) ausgelöst. Dieses Ereignis wird unabhängig davon ausgelöst, ob der Drag erfolgreich ist.
Wenn die dropEffect-Eigenschaft während eines dragend den Wert none hat, wurde der Drag abgebrochen. Andernfalls gibt der Effekt an, welche Operation durchgeführt wurde. Die Quelle kann diese Information nach einer move-Operation verwenden, um das gezogene Element von der alten Position zu entfernen.
Ein Drop kann innerhalb desselben Fensters oder über einer anderen Anwendung erfolgen. Das dragend-Ereignis wird jedoch immer ausgelöst. Die screenX- und screenY-Eigenschaften des Ereignisses werden auf die Bildschirmkoordinaten gesetzt, an denen der Drop erfolgte.
Nachdem das dragend-Ereignis die Verbreitung abgeschlossen hat, ist die Drag-and-Drop-Operation abgeschlossen.