W3docs

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 mit touchstart/touchmove/touchend erweitern.
  • Native HTML5 Drag and Drop (draggable="true" + dragstart/dragover/drop) — der Browser übernimmt das „Geist"-Bild und die Ziehgeste für dich und stellt ein DataTransfer-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

  1. Ziehen starten:
    • Der Vorgang beginnt, wenn der Nutzer auf das Element klickt und die Maustaste gedrückt hält.
  2. Das Element ziehen:
    • Während sich die Maus bewegt, folgt das Element dem Cursor über den Bildschirm.
  3. 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.

Info

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:

  1. 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).
  2. 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 setzt active = true. Der Versatz ist wichtig — ohne ihn würde das Symbol bei jedem neuen Ziehvorgang zum Ursprung zurückspringen.
  3. Bewegen (mousemove): Solange active true ist, berechnet das Skript, wie weit sich der Cursor bewegt hat, und wendet das als translate3d-Transformation an, sodass das Symbol dem Zeiger folgt. Ist active false, kehrt der Handler sofort zurück und tut nichts.
  4. Ablegen (mouseup): Wenn du die Taste loslässt, speichert das Skript die Endposition, setzt active = false und ruft isInside auf, 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.
  5. Treffertests (isInside): Dieser Helfer vergleicht die umgebenden Rechtecke der beiden Elemente mit getBoundingClientRect() und gibt true nur zurück, wenn die Glühbirne vollständig innerhalb der Ränder des dunklen Bereichs liegt.
Info

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:

  1. 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.
  2. 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.
  3. 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:

EventFeuert aufWann
dragstartdas gezogene Elementim Moment, in dem das Ziehen beginnt
dragoverdas Drop-Targetkontinuierlich, während ein Draggable darüber ist
dropdas Drop-Targetwenn der Nutzer darüber loslässt
dragenddas gezogene Elementwenn 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));
});
Warnung

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.

Übungen

Übung
Welche Aussagen über die Drag-and-Drop-Funktionalität in JavaScript sind wahr?
Welche Aussagen über die Drag-and-Drop-Funktionalität in JavaScript sind wahr?
Was this page helpful?