JavaScript/Drag & Drop
Wussten Sie eigentlich schon, dass sich Bilder, Anker und markierter Text auch ohne spezielle Festlegung mit der Maus ziehen lassen? Probieren Sie es mit dem Logo links oben und den Elementen dieser Webseite einmal aus! Es ist allerdings nicht möglich, diese Elemente irgendwo abzulegen (mit Ausnahme eines input-Elements).
Seit die Computer-Maus das Ziehen und Ablegen von Bildern und Icons ermöglichte, erfreut sich diese intuitive und benutzerfreundliche Methode großer Beliebtheit. Dieses Drag & Drop wurde von Microsoft schon 1999 im IE5 eingeführt. In HTML5 sorgt die Drag & Drop-API nun für nativen Browser-Support, ohne dass externe Frameworks notwendig sind.[1]
Trotz kritischer Stimmen, die die Menge der möglichen Events und die umständliche Handhabung beim Ablegen bemängeln, ist eine einheitliche Spezifikation und nativer Browser-Support doch zu begrüßen.[2] Grobe Ungereimtheiten wie eine CSS-Deklaration -khtml-user-drag: element;
als Ersatz für das damals in Safari fehlende draggable-Attribut sind mittlerweile nicht zuletzt dank der einheitlichen Spezifikation beseitigt.
Inhaltsverzeichnis
HTML
Eigentlich ist Drag und Drop eine HTML5-API, die bereits im HTML ansetzt. Dafür gibt es ein neues Universalattribut. Ein zweites wurde vorgeschlagen, aber nicht implementiert und wieder aus dem Standard entfernt.
draggable
Mit dem neuen Universalattribut draggable="true"
können Sie jedes Element ziehbar machen, bzw. das Ziehen von Bildern und Links durch Setzen von draggable="false"
unterbinden.
Folgende Werte sind erlaubt:
true
: das Element kann gezogen und verschoben werdenfalse
: das Element kann nicht gezogen und verschoben werden
<img id="drag1" src="Selfhtml-logo.gif" alt="Spinnen-Logo" draggable="false" ondragstart="drag(event)">
<img id="drag2" src="2006_patrick_rudolph.gif" draggable="true" ondragstart="drag(event)">
<img id="drag3" src="2006-ani_kirsten-evers.gif" draggable="true" ondragstart="drag(event)">
Im Beispiel sind die beiden Weihnachts-Logos mit draggable=true
ziehbar gemacht worden.
Um zu verdeutlichen, was gezogen werden darf und was nicht, können Sie mit CSS die Darstellung ändern:
[draggable=true] {
cursor: move;
}
[draggable=false] {
cursor: not-allowed;
}
Mit der CSS-Eigenschaft cursor:move
wird der Maus-Zeiger während des Ziehens angepasst. Die Einstellung cursor: not-allowed;
signalisiert bei draggable="false"
das Ziehverbot.
Erzeugen einer Ablagezone (das ehemalige dropzone Attribut)
Ohne besondere Maßnahmen zu ergreifen, können Sie gezogene Objekte in input- und textarea- sowie mit contenteditable ausgezeichnete Elementen ablegen.
Das dropzone-Attribut war dafür vorgesehen, weitere Ablageorte für gezogene Objekte deklarativ im Markup festzulegen. Die Versionen 5 und 5.1 des HTML Standards haben es aufgeführt, es wurde aber in keinem Browser implementiert und 2017 mit HTML 5.2 entfernt.
Die Begründung für die Entfernung war: Man muss dafür ohnehin JavaScript schreiben. Und dann kann man auch Script verwenden, um den Drop zu ermöglichen. Das geschieht so, dass man einen Handler für das dragover-Event registriert und für einen erwünschten Drop das Standardverhalten des Browsers (das im Verhindern des Drop besteht) unterbindet:
<div id="ziel" ondrop="drop(event)" ondragover="allowDrop(event)"></div>
<script>
function allowDrop(ev) {
ev.preventDefault();
}
function drag(ev) {
ev.dataTransfer.setData('text', ev.target.id);
}
function drop(ev) {
ev.preventDefault();
let data = ev.dataTransfer.getData('text');
ev.target.appendChild(document.getElementById(data));
}
</script>
JavaScript
Bereits hier ist klar, dass Drag und Drop nur mit dem Einsatz von JavaScript funktioniert.
drag-Events
Die API bietet eine Vielzahl von Events, um das Ziehen und Ablegen möglichst genau steuern zu können.
-
dragstart
: Sobald Sie ein Element mit gedrückter Maus ziehen, wird derdragstart
-Event gefeuert. Er übergibt dem dataTransfer-Objekt die nötigen Informationen. -
drag
: feuert, solange Sie ein Element oder eine Textauswahl ziehen. Das mit event.Target ermittelte Target ist das bewegte Element. -
dragover
: löst aus, wenn Sie ein Element oder eine Textauswahl auf/über ein gewünschtes Ziel gezogen haben. Das mit event.Target ermittelte Target ist das Element, über dem man gerade mit dem bewegten Element schwebt.Beachten Sie: Das ist ein entscheidender Unterschied, denn in dragover kann man prüfen, ob der Cursor gerade über einem drop-fähigen Element schwebt (mangels droppable-Attribut) und entsprechend den dropEffect setzen. -
dragenter
: löst aus, wenn Sie ein Element oder eine Textauswahl zum gewünschten Ziel gezogen haben. -
dragleave
: dragleave feuert, wenn Sie ein Element oder eine Textauswahl wieder vom gewünschten Ziel wegziehen. -
drop
: löst aus, wenn Sie ein Element oder eine Textauswahl abgelegt haben. -
dragend
: wird ausgelöst, wenn der Ziehvorgang vorzeitig abgebrochen wird.
const anzeige1 = document.getElementById('anzeige1'),
anzeige2 = document.getElementById('anzeige2'),
anzeige3 = document.getElementById('anzeige3'),
anzeige4 = document.getElementById('anzeige4');
document.addEventListener('dragstart', function(event) {
event.dataTransfer.setData('Text', event.target.id);
anzeige1.innerHTML = 'dragstart: ausgelöst';
anzeige1.className = 'start';
});
document.addEventListener('drop', function (event) {
event.preventDefault();
let dropziel = event.target.closest('.dropziel');
if (dropziel) {
let draggedId = event.dataTransfer.getData('Text');
dropziel.appendChild(document.getElementById(draggedId));
// ...
}
});
document.addEventListener('drag', function(event) {
anzeige2.innerHTML = 'drag: wird gezogen';
anzeige2.className = 'aktion';
});
document.addEventListener('dragenter', function(event) {
let dropziel = event.target.closest('.dropziel');
if (dropziel) {
anzeige4.innerHTML = 'dragenter:<br> "Sie haben ihr Ziel erreicht"';
anzeige4.className = 'ziel';
dropziel.classList.add('erreicht');
}
});
document.addEventListener('dragover', function (event) {
event.preventDefault();
anzeige3.innerHTML = 'dragover: über einem HTML-Element!<br> event.Target:' + event.target.nodeName;
});
document.addEventListener('dragleave', function (event) {
// Der ?. Operator wäre schicker aber der ACE Editor im Frickl macht das kaputt
let dropziel = event.target.closest('.dropziel');
let related = event.relatedTarget && event.relatedTarget.closest('.dropziel');
if (dropziel != null && related == null) {
anzeige4.innerHTML = 'dragleave: <br>"Sie gehen ja wieder weg!"';
anzeige4.className = 'wiederweg';
dropziel.classList.remove('erreicht');
}
});
...
dragenter
und dragleave
muss man beachten, dass das gezogene Element eventuell über Kindelemente des Dropbereichs gezogen wird. Wenn man in diesen Eventhandlern direkt auf event.target zugreift, würde man nicht auf den Dropbereich zugreifen, sondern auf eins seiner Kindelemente. Deswegen ist hier noch ein Aufruf von closest('.dropziel') nachgeschaltet, um die Elternkette soweit hochzugehen, bis das div für den Dropbereich gefunden wird (oder das Ende der Kette erreicht ist, wenn man außerhalb ist).dragleave
kommt als weitere Besonderheit hinzu, dass dragleave auch dann feuert, wenn man in ein Kind-Element des Dropbereichs hineinzieht. Das ist nicht falsch, man verlässt den Dropbereich und betritt das Kindelement. Unser Beispiel erlaubt aber, auch auf Kindelemente des Dropbereichs zu droppen, deswegen wäre der Hinweis, dass man wieder weggeht, falsch, wenn man ein Kindelement betritt. Hier hilft die relatedTarget
des Eventobjekts weiter. Bei einem dragleave
Event findet sich hier das Element, in das hineingezogen wird. Die Abfrage lautet also: Verlässt man den Dropbereich (oder ein Kindelement davon) und zieht in etwas hinein, das nicht zum Dropbereich gehört? Nur dann hat man den Dropbereich wirklich verlassen.DataTransfer Objekt
Der Vorgang des Ziehens wäre nicht möglich, ohne dass die Ereignisse in einem DataTransfer
-Objekt gespeichert werden können. Deshalb haben alle drag-Events eine dataTransfer
-Eigenschaft, die mit folgenden Methoden gesetzt und ausgelesen werden kann:
dataTransfer.effectAllowed=value
: gibt den Typ der erlaubten Aktion zurück. Mögliche Werte sind:none
: Objekt darf nicht bewegt werdencopy
: kopiert Objekt in die DropzonecopyLink
: copy oder link ist erlaubtcopyMove
: copy oder move ist erlaubtlink
: ein Link kann an der neuen Stelle eingerichtet werdenlinkMove
: link oder move ist erlaubtmove
: darf bewegt werdenall
: alle Funktionen sind erlaubtuninitialized
: alle Funktionen sind erlaubt
dataTransfer.setData(Format, Data)
: setzt Daten und ihr Format. Mögliche Werte wären:text/plain
text/uri-list
image/jpeg
dataTransfer.getData(Format)
: gibt Datenwerte ausdataTransfer.clearData(Format)
: entfernt gepeicherte DatenwertedataTransfer.setDragImage(Element, x, y)
: erzeugt ein ziehbares Bild, die x und y Koordinaten legen den Ort des Mauszeigers fest (0, 0 wäre die linke, obere Ecke).
Anwendungsbeispiel
Die oberen Beispiele sollten nur demonstrieren, wie wenig JavaScript-Code nötig ist. Im Folgenden sehen Sie nun, wie ein solches Beispiel logisch aufgebaut ist:
<div id="div1" ondrop="ablegen(event)" ondragover="ablegenErlauben(event)">
<p draggable="true" ondragstart="ziehen(event)" id="drag1"></p>
<p draggable="true" ondragstart="ziehen(event)" id="drag2"></p>
<p draggable="true" ondragstart="ziehen(event)" id="drag3"></p>
</div>
<div id="div2" ondrop="ablegen(event)" ondragover="ablegenErlauben(event)">
<p draggable="true" ondragstart="ziehen(event)" id="drag4"></p>
<p draggable="true" ondragstart="ziehen(event)" id="drag5"></p>
<p draggable="true" ondragstart="ziehen(event)" id="drag6"></p>
</div>
function ablegenErlauben(ev) {
ev.preventDefault();
}
function ziehen(ev) {
ev.dataTransfer.setData("text", ev.target.id);
}
function ablegen(ev) {
ev.preventDefault();
let data = ev.dataTransfer.getData("text");
ev.target.appendChild(document.getElementById(data));
}
Die Events sind mit Event-Attributen an die HTML-Elemente gehängt, im nächsten Beispiel zeigen wir Ihnen, wie es eleganter geht.
Grundschritte
Zuerst müssen Sie die Kugeln mittels draggable
ziehbar machen.
<p draggable="true" id="kugel1"> </p>
Im oberen Beispiel wurden die Ereignisse durch Event-Attribute angehängt – hier wird das Ganze dynamisch erledigt:
window.addEventListener("load",function () {
for (const draggable of document.querySelectorAll("[draggable=true]")) {
draggable.addEventListener("dragstart",ziehen);
};
});
draggable="true"
ermittelt. Das Ergebnis ist eine NodeList, über die nun mit einer for...of Schleife itieriert wird. In der Schleife wird an jedes gefundene Element durch addEventListener der Event-Handler angebunden.
function ziehen(ev) {
ev.dataTransfer.setData('text', ev.target.id);
}
ziehen()
setzt die dataTransfer-Eigenschaft. Auch wenn in den Kugeln kein Text vorhanden ist, gelten sie doch als Text.Ablegen
Sobald Sie sich mit dem gezogenen Objekt über der Zielzone befinden, wird das dragover
-Event ausgelöst. Um das Ablegen zu ermöglichen, müssen Sie dieses mit preventDefault "abschalten".
Erst dann darf abgelegt werden:
function ablegenErlauben(ev) {
ev.preventDefault();
}
function ablegen(event) {
event.preventDefault();
const kugelId = event.dataTransfer.getData('text');
const target = event.currentTarget;
target.appendChild(document.getElementById(kugelId));
}
document.addEventListener("DOMContentLoaded", function () {
for (const zielzone of document.querySelectorAll(".zielzone")) {
zielzone.addEventListener("drop", ablegen);
zielzone.addEventListener("dragover", ablegenErlauben);
};
for (const draggable of document.querySelectorAll("[draggable=true]")) {
draggable.addEventListener("dragstart", ziehen);
};
});
ablegenErlauben()
wird das die Browser-Reaktion auf das dragover-Event mit preventDefault abgeschaltet. Dann kann abgelegt werden. In der Funktion ablegen()
wird der Wert der dataTransfer-Eigenschaft mit getData('text') ausgelesen. Beim Ziehen wird dort die ID der gezogenen Kugel hinterlegt, so dass sich die gezogene Kugel nun über ihre ID auffinden lässt. Zum Ablegen wird das DOM Element, das die Kugel abbildet, als neues Kind-Element des div eingefügt, in dem abgelegt wird.Das lag daran, dass in der Funktion
ablegen()
das falsche Event-Target verwendet wurde. Die Eigenschaft target
des Event-Objekts zeigt auf das Element, auf dem das Event ausgelöst wurde. Je nach Mausposition kann das das Ablage-div oder eine der anderen Kugeln sein. Es gibt aber auch die Eigenschaft currentTarget
. Darin steht, auf welchem Element das Event behandelt wird (also wo der Eventhandler registriert ist). Nutzt man diese Eigenschaft, wird die Kugel immer in das div Element gesetzt.Siehe auch
- File Upload - Auswahl mit Drag und Drop
- JavaScript/Tutorials/Drag and Drop - Punkte und Elemente frei verschieben und ablegen
- Drag & Drop mit SVG:
Weblinks
- ↑ W3C: Drag and Drop
- ↑ quirksmode: The HTML5 drag and drop disaster
- ↑ caniuse: Drag and Drop
- WHATWG: 6.9 Drag and drop
- MDN: HTML Drag and Drop API
draggable
auffalse
gesetzt wurde.Wichtig für die Funktion von Drag & Drop sind nur die beiden ersten Funktionen. Die übrigen dienen nur dazu, die Abläufe während der Operation zu kommentieren. Dazu dienen vier p-Elemente mit den IDs anzeige1 bis anzeige4, deren Elementobjekte für einfacheren Zugriff in Variablen gespeichert werden.