JavaScript/Drag & Drop

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

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 durch komplizierte Skripte oder die Einbindung fertiger Frameworks ermöglicht. Microsoft veröffentlichte schon 1999 einen eigenen Entwurf für den IE5, der von anderen Browsern übernommen wurde. In HTML5 ermöglicht die Drag & Drop-API nun für nativen Browser-Support, ohne dass externe Frameworks notwendig sind.[1]

  • HTML5
  • Chrome
  • Firefox
  • IE 9
  • Opera
  • Safari

Details: caniuse.com

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] Gröbste 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.

Beachten Sie: Auch wenn die Browser-Unterstützung gut aussieht, ist HTML5-Drag & Drop auf mobilen Geräten mit iOS und Android (noch) nicht möglich.[3]


Inhaltsverzeichnis

[Bearbeiten] Allgemeines

Wussten Sie eigentlich schon, dass sich Bilder, Anker und markierter Text auch ohne spezielle Festlegung ziehen lassen? Probieren Sie es mit dem Iconset und den Links bzw. Text in diesem Absatz einmal aus!

Beispiel ansehen …
    <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();
    var data = ev.dataTransfer.getData("text");
    ev.target.appendChild(document.getElementById(data));
}
In diesem Beispiel können Sie die Kugeln (p-Elemente mit 50% border-radius) beliebig zwischen den weißen div-Feldern hin- und her schieben und ablegen.
Die Events sind mit Event-Attributen an die HTML-Elemente gehängt, im nächsten Beispiel zeigen wir Ihnen, wie es eleganter geht.

[Bearbeiten] 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 werden
  • false: das Element kann nicht gezogen und verschoben werden

[Bearbeiten] dropzone

Sie können gezogene Objekte in input- und textarea- sowie mit contenteditable ausgezeichnete Elemente ablegen. Ein in der HTML5-Spezifikation befindliches dropzone-Attribut wurde noch von keinem Browser implementiert.

  • Achtung

Wenn Sie andere Elemente zum Ablegen verwenden wollen, müssen Sie diese mit dem drop-Event verbinden.


[Bearbeiten] 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 der dragstart-Event gefeuert. Er übergibt dem dataTransfer-Objekt die nötigen Informationen.
  • drag: feuert, solange Sie ein Element oder eine Textauswahl ziehen.
  • dragover: soll auslösen, wenn Sie ein Element oder eine Textauswahl auf/über ein gewünschtes Ziel gezogen haben. Allerdings hat schon Peter-Paul Koch bemerkt, dass er eigentlich genauso wie drag funktioniert.[4]
    Empfehlung: Vermeiden Sie den Gebrauch von dragover.
  • dragenter: löst aus, wenn Sie ein Element oder eine Textauswahl zum gewünschten Ziel gezogen haben.
  • dragleave: dragenter 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.
Beispiel ansehen …
document.addEventListener('dragstart', function(event) {
    event.dataTransfer.setData('Text', event.target.id);
	document.getElementById('anzeige1').innerHTML = 'dragstart: ausgelöst';
	document.getElementById('anzeige1').className = 'start';
});
 
document.addEventListener('drag', function(event) {
    event.dataTransfer.setData('Text', event.target.id);
	document.getElementById('anzeige2').innerHTML = 'drag: wird gezogen';
	document.getElementById('anzeige2').className = 'aktion';
});
 
document.addEventListener('dragenter', function(event) {
    if ( event.target.className == "droptarget" ) {
	document.getElementById('anzeige4').innerHTML = 'dragenter:<br> "Sie haben ihr Ziel erreicht"';
	document.getElementById('anzeige4').className = 'ziel';
    event.target.style.backgroundColor = '#ebf5d7';
    }
});
 
...
Sie können nur den unteren der beiden (Ab-)Sätze ziehen, da beim oberen das Attribut draggable auf false gesetzt wurde.
Sobald ein Drag-Event ausgelöst wird, wird die dazu passende Funktion aufgerufen, die dann die Ereignisse kommentiert.

Um zu verdeutlichen, was gezogen werden darf und was nicht, können Sie mit CSS die Darstellung ändern:

Beispiel ansehen …
[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.

[Bearbeiten] dataTransferObject

Der Vorgang des Ziehens wäre nicht möglich, ohne dass die Ereignisse in einem dataTransferObject 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 werden
    • copy: kopiert Objekt in die Dropzone
    • copyLink: copy oder link ist erlaubt
    • copyMove: copy oder move ist erlaubt
    • link: ein Link kann an der neuen Stelle eingerichtet werden
    • linkMove: link oder move ist erlaubt
    • move: darf bewegt werden
    • all: alle Funktionen sind erlaubt
    • uninitialized: 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 aus
  • dataTransfer.clearData(Format): entfernt gepeicherte Datenwerte
  • dataTransfer.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).

[Bearbeiten] Funktionsweise

Das obere Beispiel sollte nur demonstrieren, wie wenig JavaScript-Code nötig ist. Im Folgenden sehen Sie nun, wie ein solches Beispiel logisch aufgebaut ist:


[Bearbeiten] Grundschritte

Zuerst müssen Sie die Kugeln mittels draggable ziehbar machen.

Beispiel
<p draggable="true" id="kugel1"> </p>

Im oberen Beispiel wurden die Ereignisse durch Event-Attribute angehängt - hier wird das Ganze dynamisch erledigt:

Beispiel
window.addEventListener("load",function () {
  var elms = document.querySelectorAll("[draggable=true]")
  for (var i = 0; i < elms.length; i++) {
    var draggable = elms[i];
    draggable.addEventListener("dragstart",ziehen);
  };
});
Nach dem Laden des Dokuments werden mittels querySelectorAll alle Elemente mit dem Attribut draggable="true" ermittelt. In einer Schleife werden dann durch addEventListener die Event-Handler dynamisch angebunden.


Beispiel
  function ziehen(ev) {
    ev.dataTransfer.setData('text', ev.target.id);
  }
Die Funktion ziehen() setzt die dataTransfer-Eigenschaft. Auch wenn in den Kugeln kein Text vorhanden ist, gelten sie doch als Text.

[Bearbeiten] 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:

Beispiel ansehen …
function ablegenErlauben(ev) {
	ev.preventDefault();
}
 
function ablegen(ev) {
    ev.preventDefault();
    var data = ev.dataTransfer.getData('text');
    var target = ev.target;
    while (" "+target.className+" ".indexOf(" dropzone ") == -1) target = target.parentNode;
    target.appendChild(document.getElementById(data));
}
 
window.addEventListener("load",function () {
	var elms = document.querySelectorAll(".zielzone");
	for (var i = 0; i < elms.length; i++) {
		var zielzone = elms[i];
		zielzone.addEventListener("drop",ablegen);
		zielzone.addEventListener("dragover",ablegenErlauben);
	};
 
	elms = document.querySelectorAll("[draggable=true]")
	for (var i = 0; i < elms.length; i++) {
		var draggable = elms[i];
		draggable.addEventListener("dragstart",ziehen);
	};
});
In der Funktion ablegenErlauben() wird das dragover-Event mit preventDefault abgeschaltet. Dann kann abgelegt werden. In der Funktion ablegen() wird der Wert der dataTransfer-Eigenschaft mit getData('text') ausgelesen und die Kugel dann abgelegt.


Ist Ihnen aufgefallen, dass man im oberen Beispiel eine Kugel auch innerhalb einer anderen Kugel ablegen konnte?
Das lag nur daran, dass in der Funktion ablegen() das target nicht explizit auf das div eingestellt worden war - was sich mit zwei zusätzlichen Zeilen problemlos beheben lässt:

Das " "+target.className+" ".indexOf(" zielzone ") == -1 ist nichts anderes als ein relativ smarter Test darauf, ob ein Element (target) die Klasse zielzone enthält; da className ein String mit den Leerzeichenseparierten Klassennamen ist, findet diese Methode ein zielzone sowohl in "zielzone andereKlasse" als auch in "andereKlasse zielzone dritteKlasse" als auch in "zielzone", da am vorderen String pauschal Leerzeichen angehängt wurden. Das ist leider noch nötig, weil IE9 noch nicht offiziell tot ist und kein element.classList unterstützt - ansonsten wäre target.classList.contains("zielzone") das Mittel der Wahl.


[Bearbeiten] Anwendungsbeispiele

[Bearbeiten] Weblinks

  1. W3C: Drag and Drop
  2. quirksmode: The HTML5 drag and drop disaster
  3. caniuse: Drag and Drop
  4. quirksmode: dragbullshit


Meine Werkzeuge
Namensräume

Varianten
Aktionen
Übersicht
Index
Mitmachen
Werkzeuge
Spenden
SELFHTML