JavaScript Drag and Drop
Drag-and-Drop in JavaScript: Maus-Events (mousedown/mousemove/mouseup) und die native HTML5 Drag and Drop API mit draggable, dragover, drop und DataTransfer — mit praktischem Beispiel.
In diesem Tutorial erkunden wir die Drag-and-Drop-Funktionalität in JavaScript, ein leistungsstarkes Feature, das die Interaktivität auf Webseiten erhöht. Es gibt zwei Möglichkeiten, sie umzusetzen: den Maus-Event-Ansatz (mousedown/mousemove/mouseup), der dir vollständige Kontrolle über die Bewegung gibt, und die native HTML5 Drag and Drop API (draggable + die drag*-Events), die im Browser integriert ist. Wir behandeln beide, sehen wo jede Methode passt, und bauen dann ein praktisches Beispiel, bei dem ein Glühbirnen-Symbol einen dunklen Bereich erhellt, wenn man es hineinzieht.
Was ist Drag-and-Drop?
Drag-and-Drop ist eine Benutzeroberflächen-Interaktion, die es Nutzern ermöglicht, ein Objekt zu greifen und an eine andere Stelle auf dem Bildschirm zu ziehen. Man begegnet ihr überall: beim Verschieben von Dateien im Betriebssystem, beim Neuanordnen von Elementen in einem Spiel, beim Hochladen von Fotos durch Ablegen auf einer Seite oder beim Umsortieren einer To-do-Liste. Das Muster hat immer drei Rollen:
- Ein Draggable — das Element, das der Nutzer aufgreift.
- Ein Drop-Target (oder Droppable) — der Bereich, der es aufnehmen kann.
- Eine Payload — optionale Daten, die von der Quelle zum Ziel transportiert werden (zum Beispiel die ID des verschobenen Elements).
Den richtigen Ansatz wählen
Beide Techniken sind gültig; die Wahl hängt von deinen Anforderungen ab.
- Maus-Events (
mousedown,mousemove,mouseup) — du verfolgst den Zeiger manuell und bewegst das Element selbst. Verwende diesen Ansatz, wenn das Element dem Cursor pixelgenau folgen muss (Regler, Freihand-Leinwände, Zeichenwerkzeuge, benutzerdefinierte Physik). Er lässt sich außerdem auf Touchscreens mittouchstart/touchmove/touchenderweitern. - Native HTML5 Drag and Drop (
draggable="true"+dragstart/dragover/drop) — der Browser übernimmt das „Geist"-Bild und die Ziehgeste für dich und stellt einDataTransfer-Objekt zum Übertragen von Daten bereit. Verwende dies zum Übertragen von Elementen zwischen Bereichen (Datei-Uploads, Listen umsortieren, Elemente in einen Warenkorb legen). Auf Touch-Geräten funktioniert es nicht ohne Weiteres.
Wir demonstrieren den Maus-Event-Ansatz im Hauptbeispiel und fassen danach die native API zusammen.
Grundkonzepte von Drag-and-Drop in JavaScript
Der Drag'n'Drop-Algorithmus
- Ziehen starten:
- Der Vorgang beginnt, wenn der Nutzer auf das Element klickt und die Maustaste gedrückt hält.
- Das Element ziehen:
- Während sich die Maus bewegt, folgt das Element dem Cursor über den Bildschirm.
- Das Element ablegen:
- Das Element wird freigegeben, wenn der Nutzer die Maustaste loslässt, und nimmt eine neue Position ein.
Droppables verstehen
Droppables sind Bereiche, die dazu bestimmt sind, ziehbare Elemente aufzunehmen. Diese Bereiche erkennen, wenn ein Draggable über ihnen liegt, und können daraufhin bestimmte Aktionen auslösen.
Stelle sicher, dass deine Drag-and-Drop-Funktionalität touch-freundlich ist. Mobile Nutzer sollten per Touch-Gesten ziehen und ablegen können. Erwäge die Implementierung von Touch-Events (touchstart, touchmove, touchend) oder die Verwendung einer leichten Bibliothek für geräteübergreifende Kompatibilität.
Interaktives Beispiel: Heller und dunkler Bereich
Setzen wir die Theorie mit einem einfachen, aber interaktiven Beispiel in die Praxis um. Wir verwenden ein Glühbirnen-Symbol als ziehbares Objekt. Wenn dieses Symbol über einen dunklen Bereich gezogen wird, leuchtet der Bereich auf und simuliert den Effekt einer eingeschalteten Lampe.
HTML und CSS einrichten
Zuerst definieren wir die grundlegende Struktur und den Stil. Wir fügen ein dunkles Feld und ein Glühbirnen-Symbol ein.
HTML-Struktur
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Interactive Lighting with Drag and Drop</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" />
<style>
#darkArea {
width: 300px;
height: 300px;
background-color: #333;
position: relative;
margin-top: 20px;
}
#lightIcon {
font-size: 48px;
color: #ccc;
cursor: pointer;
position: absolute;
}
</style>
</head>
<body>
<div id="main">
<div id="darkArea"></div>
<i id="lightIcon" class="fas fa-lightbulb"></i>
</div>
<script>
// JavaScript will be added here.
</script>
</body>
</html>Das JavaScript implementieren
Nun fügen wir die Funktionalität hinzu, um die Glühbirne ziehbar und reaktiv auf den dunklen Bereich zu machen.
Erklärung des JavaScript-Codes
<script>
// Get references to the light bulb icon and the dark area on the webpage
var lightIcon = document.getElementById("lightIcon");
var darkArea = document.getElementById("darkArea");
// Variables to track whether the dragging is active and to store position data
var active = false;
var initialX, initialY, currentX, currentY, xOffset = 0, yOffset = 0;
// Listen for the mouse down event on the light bulb icon
lightIcon.addEventListener("mousedown", function(e) {
// Record the starting position of the mouse and adjust by any existing offset
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
// Set the active flag to true, indicating that dragging has started
active = true;
});
// Listen for mouse movement across the entire document
document.addEventListener("mousemove", function(e) {
// If not dragging, don't do anything
if (!active) return;
// Calculate the new position of the mouse
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
// Update the offset with the new position
xOffset = currentX;
yOffset = currentY;
// Move the light bulb icon to the new position
lightIcon.style.transform = "translate3d(" + currentX + "px, " + currentY + "px, 0)";
});
// Listen for the mouse up event across the entire document
document.addEventListener("mouseup", function() {
// Save the final position of the light bulb
initialX = currentX;
initialY = currentY;
// Set the active flag to false, indicating dragging has ended
active = false;
// Check if the light bulb is inside the dark area
if (isInside(darkArea, lightIcon)) {
// Change the background color of the dark area to yellow
darkArea.style.backgroundColor = "yellow";
// Change the color of the light bulb to yellow
lightIcon.style.color = "yellow";
} else {
// Revert the dark area's color to dark
darkArea.style.backgroundColor = "#333";
// Revert the light bulb's color to gray
lightIcon.style.color = "#ccc";
}
});
// Function to check if the light bulb is inside the dark area
function isInside(container, element) {
// Get the position of the container and the element
var containerRect = container.getBoundingClientRect();
var elementRect = element.getBoundingClientRect();
// Return true if the element is within the container's boundaries
return (
elementRect.left >= containerRect.left &&
elementRect.right <= containerRect.right &&
elementRect.top >= containerRect.top &&
elementRect.bottom <= containerRect.bottom
);
}
</script>Dieses Skript macht die Glühbirne mit der Maus ziehbar und reagiert, wenn du sie über den dunklen Bereich ablegst. Hier ist, was jeder Teil tut:
- Setup: Das Skript holt Referenzen auf das Glühbirnen-Symbol und den dunklen Bereich und deklariert die Variablen zum Verfolgen des Ziehvorgangs (
active, die anfängliche Mausposition, der aktuelle Versatz). - Ziehen starten (
mousedown): Wenn du die Maustaste auf der Glühbirne drückst, speichert das Skript die Startposition des Cursors (abzüglich eines eventuellen Versatzes aus einem vorherigen Ziehvorgang) und setztactive = true. Der Versatz ist wichtig — ohne ihn würde das Symbol bei jedem neuen Ziehvorgang zum Ursprung zurückspringen. - Bewegen (
mousemove): Solangeactivetrueist, berechnet das Skript, wie weit sich der Cursor bewegt hat, und wendet das alstranslate3d-Transformation an, sodass das Symbol dem Zeiger folgt. Istactivefalse, kehrt der Handler sofort zurück und tut nichts. - Ablegen (
mouseup): Wenn du die Taste loslässt, speichert das Skript die Endposition, setztactive = falseund ruftisInsideauf, um zu entscheiden, ob die Glühbirne im dunklen Bereich gelandet ist. Wenn ja, werden sowohl der Bereich als auch die Glühbirne gelb; andernfalls werden sie auf ihre Standardfarben zurückgesetzt. - Treffertests (
isInside): Dieser Helfer vergleicht die umgebenden Rechtecke der beiden Elemente mitgetBoundingClientRect()und gibttruenur zurück, wenn die Glühbirne vollständig innerhalb der Ränder des dunklen Bereichs liegt.
Verwende translate3d für deine CSS-Transformationen beim Ziehen von Elementen. Es nutzt GPU-Beschleunigung, was zu flüssigeren Bewegungen und geringerer CPU-Last führt — entscheidend für leistungsintensive Anwendungen.
Vollständiges Beispiel
Jetzt ist es Zeit, alles in Aktion zu sehen:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Interactive Lighting with Drag and Drop</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" />
<style>
#darkArea {
width: 300px;
height: 300px;
background-color: #333;
position: relative;
margin-top: 20px;
}
#lightIcon {
font-size: 48px;
color: #ccc;
cursor: pointer;
position: absolute;
}
</style>
</head>
<body>
<div id="main">
<p>Move the light into the dark area to light it up!</p>
<div id="darkArea"></div>
<i id="lightIcon" class="fas fa-lightbulb"></i>
</div>
<script>
// Get references to the light bulb icon and the dark area on the webpage
var lightIcon = document.getElementById("lightIcon");
var darkArea = document.getElementById("darkArea");
// Variables to track whether the dragging is active and to store position data
var active = false;
var initialX,
initialY,
currentX,
currentY,
xOffset = 0,
yOffset = 0;
// Listen for the mouse down event on the light bulb icon
lightIcon.addEventListener("mousedown", function (e) {
// Record the starting position of the mouse and adjust by any existing offset
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
// Set the active flag to true, indicating that dragging has started
active = true;
});
// Listen for mouse movement across the entire document
document.addEventListener("mousemove", function (e) {
// If not dragging, don't do anything
if (!active) return;
// Calculate the new position of the mouse
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
// Update the offset with the new position
xOffset = currentX;
yOffset = currentY;
// Move the light bulb icon to the new position
lightIcon.style.transform = "translate3d(" + currentX + "px, " + currentY + "px, 0)";
});
// Listen for the mouse up event across the entire document
document.addEventListener("mouseup", function () {
// Save the final position of the light bulb
initialX = currentX;
initialY = currentY;
// Set the active flag to false, indicating dragging has ended
active = false;
// Check if the light bulb is inside the dark area
if (isInside(darkArea, lightIcon)) {
// Change the background color of the dark area to yellow
darkArea.style.backgroundColor = "yellow";
lightIcon.style.color = "yellow";
} else {
// Revert the dark area's color to dark
darkArea.style.backgroundColor = "#333";
// Revert the light bulb's color to gray
lightIcon.style.color = "#ccc";
}
});
// Function to check if the light bulb is inside the dark area
function isInside(container, element) {
// Get the position of the container and the element
var containerRect = container.getBoundingClientRect();
var elementRect = element.getBoundingClientRect();
// Return true if the element is within the container's boundaries
return elementRect.left >= containerRect.left && elementRect.right <= containerRect.right && elementRect.top >= containerRect.top && elementRect.bottom <= containerRect.bottom;
}
</script>
</body>
</html>Verwendete wichtige Maus-Events:
mousedown: Dieses Event wird ausgelöst, wenn der Nutzer die Maustaste über dem Glühbirnen-Symbol drückt. Es markiert den Beginn des Ziehens und speichert die Startposition des Cursors.mousemove: Dieses Event feuert, wenn die Maus bewegt wird. Ist das Ziehen aktiv (d. h. die Maustaste ist noch gedrückt), berechnet es die neue Position des Symbols basierend auf der Cursorbewegung und aktualisiert die Position der Glühbirne auf dem Bildschirm.mouseup: Dieses Event tritt auf, wenn der Nutzer die Maustaste loslässt, und markiert das Ende des Ziehens. Es prüft, ob die Glühbirne innerhalb der Grenzen des dunklen Bereichs liegt, um zu entscheiden, ob die Hintergrundfarbe des Bereichs geändert werden soll.
Wie wir im Artikel Maus-Events gelernt haben, sind diese Events grundlegend für die Erstellung interaktiver Drag-and-Drop-Funktionalität und ermöglichen es, Elemente auf einer Webseite dynamisch mit der Maus zu verschieben und damit zu interagieren. (Wenn du das Element präzise positionieren möchtest, erklärt das Kapitel JavaScript-Koordinaten den Unterschied zwischen clientX/clientY, pageX/pageY und getBoundingClientRect().)
Die native HTML5 Drag and Drop API
Die Maus-Event-Technik ist hervorragend, wenn ein Element dem Cursor exakt folgen soll. Wenn das eigentliche Ziel jedoch darin besteht, etwas zu übertragen — eine Karte in eine Spalte, ein Element in einen Warenkorb oder eine Datei in eine Upload-Zone zu legen — ist die eingebaute Drag and Drop API des Browsers weniger Code und übernimmt die Ziehgeste für dich.
Ein Element ziehbar machen
Jedes Element wird durch Setzen des draggable-Attributs auf true ziehbar. Links und Bilder sind standardmäßig ziehbar; alles andere nicht.
<div id="item" draggable="true">Drag me</div>
<div id="dropzone">Drop here</div>Die Drag-and-Drop-Events
Die API löst eine Abfolge von Events aus. Die wichtigsten sind:
| Event | Feuert auf | Wann |
|---|---|---|
dragstart | das gezogene Element | im Moment, in dem das Ziehen beginnt |
dragover | das Drop-Target | kontinuierlich, während ein Draggable darüber ist |
drop | das Drop-Target | wenn der Nutzer darüber loslässt |
dragend | das gezogene Element | wenn das Ziehen endet (abgelegt oder abgebrochen) |
Daten mit DataTransfer übertragen
Jedes Drag-Event stellt event.dataTransfer bereit, ein Objekt, mit dem in dragstart eine Payload angehängt und in drop wieder ausgelesen werden kann.
const item = document.getElementById("item");
const zone = document.getElementById("dropzone");
// 1. Attach a payload when the drag starts.
item.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text/plain", item.id);
});
// 2. By default elements are NOT valid drop targets.
// Prevent the default to allow a drop.
zone.addEventListener("dragover", (e) => {
e.preventDefault();
});
// 3. Read the payload and move the element on drop.
zone.addEventListener("drop", (e) => {
e.preventDefault();
const id = e.dataTransfer.getData("text/plain");
zone.appendChild(document.getElementById(id));
});Der häufigste Fehler bei der nativen API ist das Vergessen von e.preventDefault() im dragover-Handler. Ohne ihn lehnt der Browser das Element als Drop-Target ab und das drop-Event feuert nie. Die mit setData gesetzten Daten sind erst wieder verfügbar, wenn drop ausgeführt wird — aus Sicherheitsgründen können sie während dragover nicht gelesen werden.
Das Paar setData(format, value) / getData(format) erlaubt es, einfachen Text, URLs (text/uri-list), HTML oder eigene benutzerdefinierte String-Schlüssel zu übertragen. Für Datei-Uploads liest man e.dataTransfer.files, was ein FileList ist, genau wie bei einem <input type="file">.
Maus-Events vs. native API auf einen Blick
- Greife auf Maus-Events zurück, wenn die Bewegung frei und pixelgenau sein muss oder wenn du Touch-Unterstützung benötigst.
- Greife auf die native API zurück, wenn du ein diskretes Element von einem Ort an einen anderen bewegst und der Browser das Drag-Bild und die Geste verwalten soll.
Um tiefer in das zugrunde liegende Event-System einzutauchen, sieh dir Einführung in Browser-Events an. Für barrierefreie Interaktionen bedenke, dass Drag-and-Drop immer eine Tastaturalternative haben sollte — siehe DOM-Barrierefreiheitsüberlegungen.
Fazit
Drag-and-Drop macht Oberflächen direkt und intuitiv. Du hast jetzt zwei Werkzeuge dafür: den Maus-Event-Ansatz, der ein Element pixelweise unter vollständiger manueller Kontrolle bewegt, und die native HTML5 Drag and Drop API, die den Browser die Geste verwalten lässt, während du Daten über DataTransfer überträgst. Wähle Maus-Events für freie Bewegung und Touch-Unterstützung und die native API für das Übertragen von Elementen zwischen Bereichen. Unabhängig davon, was du wählst, biete eine tastaturzugängliche Alternative an, damit jeder Nutzer dieselbe Aufgabe erledigen kann.