Manche Entscheidungen in der Softwareentwicklung sehen auf den ersten Blick unnötig aus. Warum eine eigene Bibliothek schreiben, wenn es fertige gibt? Warum Aufwand in etwas stecken, das andere längst gelöst haben?
Die Antwort hat weniger mit Stolz zu tun als mit dem, was man eigentlich braucht. Und mit dem, was man lieber nicht mitnimmt.
Drag-and-Drop klingt nach einer gelösten Aufgabe. Elemente anfassen, verschieben, ablegen. Das Konzept ist so vertraut, dass man kaum noch darüber nachdenkt: in CMS-Oberflächen, Aufgabenlisten, Formularbuildern, Konfigurationsseiten. Überall dort, wo der Benutzer eine Reihenfolge selbst verändern soll, statt sie bloß zu betrachten.
Technisch ist es trotzdem keine Kleinigkeit. Man muss wissen, wann jemand anfängt zu ziehen, welches Element unter dem Cursor liegt, wohin die übrigen Elemente ausweichen sollen und wann das Loslassen ein dauerhaftes Ergebnis erzeugen darf. Und das zuverlässig auf Maus, Touchscreen und Stylus.
Für genau das gibt es Bibliotheken. Wir haben trotzdem eine eigene entwickelt.
Wer nach einer Drag-and-Drop-Bibliothek sucht, landet früh bei SortableJS. Sie ist ausgereift, weit verbreitet und in vielen Projekten bewährt. Wir haben sie selbst jahrelang eingesetzt, und für eine große Zahl von Projekten ist sie nach wie vor die richtige Wahl.
Das Problem entsteht nicht beim ersten Einsatz. Es entsteht, wenn man genauer schaut.
SortableJS hat über die Jahre erheblich an Umfang gewonnen und bringt minifiziert gut 40 Kilobyte mit (1): Plugins für verschiedene Drag-Modi, eine eigene Event-Abstraktion, Rücksicht auf Browser, die seit Jahren niemand mehr ernsthaft benutzt. Das ist kein Vorwurf. Jede gewachsene Bibliothek trägt ihre Geschichte mit sich. Aber man lädt diese Geschichte mit, arbeitet mit Abstraktionsschichten, die der Browser heute nativ besser bereitstellt, und prüft bei jedem Update, ob irgendetwas davon die eigene Implementierung berührt.
Andere Kandidaten haben andere Probleme. react-beautiful-dnd von Atlassian galt lange als Referenz für React-Projekte; im Oktober 2024 wurde die Bibliothek offiziell eingestellt, inzwischen ist das Repository archiviert (2). Dragula ist angenehm minimal, wird aber seit Jahren kaum noch gepflegt. Und fast alle wurden für eine möglichst breite Zielgruppe entworfen, mit allem Ballast, den dieser Anspruch erzeugt.
Das führt zur eigentlichen Frage: Was brauchen wir wirklich?
Wir entwickeln atomic, unsere eigene Plattform für maßgeschneiderte Webanwendungen basierend auf PHP und vanilla JavaScript. Das ist eine bewusste Entscheidung: Anwendungen, die nicht an Framework-Zyklen hängen, sind langlebiger, wartbarer und in zehn Jahren noch verständlich.
In diesem Kontext ist eine Bibliothek, die React voraussetzt oder auf maximale Kompatibilität hin entworfen wurde, keine sinnvolle Grundlage. Was wir brauchen, ist etwas, das sich wie ein Browser-Feature verhält: einbinden, initialisieren, fertig.
Zugleich laufen unsere Anwendungen ausschließlich in modernen Browsern. Chrome, Firefox, Safari und Edge in aktuellen Versionen; Internet Explorer ist seit Jahren kein Thema mehr. Alles, was diese Browser nativ mitbringen, steht uns damit ohne Polyfill und ohne Workaround zur Verfügung. Einer dieser Bausteine sind die sogenannten Pointer Events.
Früher musste man in JavaScript getrennt auf Mausereignisse und Touch-Ereignisse hören. Man registrierte mousedown für die Maus, touchstart für den Touchscreen, und führte beide Wege anschließend mühsam konsistent zusammen. Das erzeugte doppelten Code, Sonderfälle und Bibliotheken, die dieses Problem einmal lösten und dann für alle Zeit mitschleppten.
Pointer Events behandeln Maus, Touch und Stylus einheitlich (3). Es gibt nur noch pointerdown, pointermove und pointerup; der Browser kümmert sich um die Unterschiede der Eingabegeräte. Chrome unterstützt Pointer Events seit Version 55, Firefox seit 59, Safari seit 13, Edge seit 79. Es gibt schlicht keinen Grund mehr, dieses Problem selbst zu lösen.
ua-sortable stützt sich vollständig auf diese Grundlage. Deshalb funktioniert das Sortieren mit der Maus, mit dem Finger und mit dem Stift, ohne Sonderbehandlung und ohne Fallbacks.
Der Kern der Bibliothek ist bewusst schmal. Sie hört auf Pointer Events, verschiebt während des Ziehens einen Platzhalter im Container und ruft am Ende einen Callback mit der neuen Reihenfolge auf. Sie funktioniert für Listen wie für Grids; die Sortierrichtung erkennt sie selbst. Alles Weitere ist Anwendungslogik und gehört dorthin, wo sie ohnehin liegt: in die Anwendung.
Unter der Haube arbeitet die Bibliothek mit den Mitteln, die der Browser dafür vorgesehen hat. Beim pointerdown übernimmt setPointerCapture die Kontrolle über den Zeiger, sodass die Bewegung auch dann weiter ankommt, wenn der Finger das Element längst verlassen hat. Ein Ghost-Klon folgt dem Zeiger, ein unsichtbarer Platzhalter markiert die Einfügeposition, berechnet über die Mittelpunkte der Nachbarelemente. Bricht das System die Geste ab, kehrt das Element an seinen Ursprung zurück.
Einige Dinge tauchen im Alltag so verlässlich auf, dass sie von Anfang an vorgesehen waren:
Drag Handles definieren einen bestimmten Bereich eines Elements als Anfasspunkt. Das ist wichtig, sobald ein Listeneintrag auch Buttons, Links oder Eingabefelder enthält.
Cross-List-Drag erlaubt das Verschieben zwischen mehreren Containern, sofern diese dieselbe Gruppenkennung tragen. Das ist die Grundlage für Kanban-Strukturen und mehrspaltige Sortierungen.
Bestätigung vor dem Ablegen schaltet vor einen Container-Wechsel eine asynchrone Prüfung, etwa einen Dialog. Praktisch bei Aktionen mit serverseitigen Konsequenzen.
Deklarative Initialisierung bedeutet: Wer lieber HTML als JavaScript schreibt, markiert Container mit einem Attribut. Ein MutationObserver erkennt sie automatisch, ebenso wie dynamisch nachgeladene Listeneinträge, ganz ohne manuelles Nachregistrieren.
Snapshot liefert auf Abruf den Zustand aller verknüpften Container: welches Element in welchem Container an welcher Position sitzt. Nützlich, wenn beim Speichern der vollständige Zustand persistiert werden soll.
Was ua-sortable nicht tut: virtuelle Listen für tausende Einträge simulieren, Touch-Scrolling in alle Richtungen kombinieren oder Framework-Bindings mitliefern. Das sind bewusste Grenzen, keine Lücken.
ua-sortable hat keine Abhängigkeiten. Das bedeutet konkret: kein node_modules, keine Lizenzkette, keine transitiven Pakete, die man nie angefordert hat und deren Sicherheitslücken trotzdem in CVE-Datenbanken auftauchen.
Die Bibliothek lässt sich per npm installieren, per CDN einbinden oder als ESM-Modul importieren. Sie funktioniert in jedem Projekt, gleichgültig ob dort React, Vue, ein eigenes Framework oder gar kein Framework läuft. Für uns bedeutet das: ua-sortable verhält sich wie ein Teil des Browsers. Man bindet sie ein, und sie tut, was sie soll, ohne eine Meinung darüber, wie der Rest der Anwendung auszusehen hat.
npm install @urbanstudio/ua-sortable
Oder ohne Installation direkt per CDN:
<script src="https://cdn.jsdelivr.net/npm/@urbanstudio/ua-sortable/dist/ua-sortable.js"></script>
Die Dokumentation im Repository beschreibt alle Optionen, Callbacks und Methoden. Eine sortierbare Liste entsteht in einer Zeile:
new UA_Sortable(document.querySelector("ul"), {
handle: ".drag-handle",
onSort: (ids) => api.saveOrder(ids),
});
Der Einwand des Not-invented-here-Syndroms liegt nahe, trifft hier aber nicht den Kern. Für viele Projekte bleibt SortableJS die richtige Entscheidung: schnell integriert, gut dokumentiert, von einer Community getragen.
Der Punkt, an dem eine eigene Lösung Sinn ergibt, ist ein spezifischer: wenn die vorhandenen Optionen mehr mitbringen, als man braucht, mehr voraussetzen, als man hat, oder sich der eigenen Architektur nur mit Reibung fügen. Für atomic war das der Fall. Die Antwort ist eine Bibliothek, die auf dem aufsetzt, was moderne Browser nativ bereitstellen, und die genau das abdeckt, was gebraucht wird.