Drag-fähige Objekte implementieren
(redblobgames.com)- Die interaktiven Diagramme von Red Blob Games müssen sofort reagieren, wenn Leser Objekte selbst bewegen; deshalb ist die Eingabeverarbeitung auf Pointer Events ausgerichtet und behandelt Maus und Touch gemeinsam
- Der frühere Ansatz mit
mousedown/mouseup/mousemovekonnte den Drag-Zustand durcheinanderbringen, wenn der Pointer das Objekt verließ oder die Taste außerhalb losgelassen wurde, weshalb Ergänzungen wie globale Handler nötig waren - Touch-Eingaben werden anders zugestellt als Mausereignisse, und ein Drag mit einem Finger löst zugleich Seitenscrolling aus; deshalb wird in
touchstartpreventDefault()aufgerufen, um Konflikte zu verringern - Das finale Rezept kombiniert
pointerdown,pointermove,pointerup,pointercancelmitsetPointerCapture()und prüft die linke Maustaste sowie den Start-Offset, damit das Objekt nicht plötzlich springt - Objekte mit Text und Bildern, Multi-Touch, verschachteltes Dragging,
<canvas>, Kombinationen aus Hover und Drag sowie plattformabhängige Sonderfälle wielostpointercapturemüssen in echten UIs separat behandelt werden
Ziele eines Drag-Eingabehandlers
- Viele interaktive Seiten enthalten ziehbare Objekte, die Leser selbst bewegen können, und das Diagramm muss auf Änderungen der Objektposition reagieren
- Ziel ist es, einen Eingabe-Event-Handler zu erstellen, der sowohl mit Maus- als auch mit Touch-Eingaben funktioniert
- Der Grundablauf besteht darin, den Drag in
pointerdownzu starten, die Position inpointermovezu aktualisieren und den Drag beipointerupoder einem Abbruch-Event zu beenden - In der tatsächlichen Implementierung wird es durch Maustasten, Touch mit mehreren Fingern, das Ziel der Event-Zustellung und Kontextmenüs per Rechtsklick komplexer als im einfachen Modell
- Das Grundrezept kombiniert die folgenden Elemente
- In
pointerdownzurückkehren, wennevent.button !== 0, und so nur die primäre Taste behandeln - Event-Koordinaten in Diagrammkoordinaten umrechnen
- Beim Start
dxunddyspeichern, also die Differenz zwischen Objektposition und Pointerposition - Mit
el.setPointerCapture(event.pointerId)den Pointer erfassen - In
pointermoveden gespeicherten Offset addieren undstate.posaktualisieren - In
pointerup,pointercancelstate.dragging = nullsetzen - In
touchstartpreventDefault()aufrufen
- In
- Dieses Rezept ist ein Eingabe-Event-Handler; die tatsächliche Ausgabe muss mit einem separaten Zustands-Handler verbunden werden
- Der vollständige Code steht auf der examples page; sie enthält auch Beispiele für horizontales Ziehen von Zahlen statt Objektbewegung, Canvas-Malen und eingeschränktes Dragging
- Der Testumfang umfasst Gecko/Firefox, Blink/Chrome und WebKit/Safari auf Mac, Windows, Linux, Android, iPhone und iPad
- Hover-fähige Stifte, hybride Touch+Maus-Geräte und Spracheingabe wurden nicht getestet
- Dieser Ansatz ist nicht die HTML-Drag and Drop API, bei der Elemente über andere Elemente gelegt werden; in Diagrammen müssen Maus- und Touch-Events direkt gelesen werden
Grenzen bei ausschließlicher Nutzung von Maus-Events
- Die erste Implementierung bestand darin,
mousedown,mouseupundmousemovedirekt am ziehbaren Element zu registrieren - Oberflächlich funktioniert das, doch es entstehen zwei Probleme
- Wenn der Pointer schnell bewegt wird, befindet er sich nicht mehr über dem Kreis und dieser erhält keine Events mehr
- Wenn die Taste außerhalb des Kreises losgelassen wird, kann der Zustand in
dragginghängen bleiben
- Die Verbesserung besteht darin,
mousedownam Kreis zu empfangen und während des Dragsmousemoveundmouseupvorübergehend andocumentzu registrieren - Wenn in
mouseupdie andocumentregistrierten Handler fürmousemoveundmouseupentfernt werden, funktioniert Maus-Dragging stabiler - Dieser Ansatz behandelt nur die Maus und berücksichtigt Touch-Events nicht
Unterschiede bei Touch-Events
- Touch-Eingaben verwenden statt
mousedown,mouseup,mousemovedie Eventstouchstart,touchend,touchmove - Touch-Events werden nach
touchstartautomatisch captured, sodass alletouchmove-Events an das ursprüngliche Element zugestellt werden - Wegen dieser Eigenschaft ist es nicht nötig, wie bei Maus-Events vorübergehend einen globalen Event-Handler an
documentzu registrieren - Wenn der Browser eine Touch-Sequenz abbrechen muss, sendet er
touchcancel - Eine Touch-Drag-Implementierung ruft in
touchstartpreventDefault()auf und aktualisiert intouchmovedie Position anhand der Koordinaten vonevent.changedTouches[0] - Dieser Ansatz behandelt nur Touch und verarbeitet Maus-Events nicht
Maus und Touch mit Pointer Events vereinheitlichen
- Um sowohl Maus als auch Touch zu behandeln, waren viele Event-Handler nötig; vor 2021 wurde dieser Ansatz verwendet
- Von 2011 bis 2014 wurde in d3-Projekten d3-drag verwendet, während Projekte ohne d3 eigenen Maus+Touch-Code schrieben
- Pointer Events vereinheitlichen und vereinfachen die Verarbeitung von Maus und Touch
- MS IE fügte 2012 Unterstützung hinzu
- Chrome fügte 2017 Unterstützung hinzu, Firefox 2018 und Safari 2020
- Das Browserverhalten hat sich im Laufe der Zeit geändert; 2017 stellte Chrome einige Events standardmäßig auf passive mode um, was dazu führte, dass die Seite scrollte, wenn man ein Objekt ziehen wollte
- Safari und Firefox übernahmen 2018 dieselbe Änderung
- Mit pointer capture lässt sich die Logik einfacher halten, ohne die bei Maus-Events nötigen globalen
document-Handler hinzuzufügen und zu entfernen
Pointer-Verfolgung mit Pointer Capture aufrechterhalten
- Pointer capture ermöglicht es, die Verfolgung fortzusetzen, auch wenn der Pointer den Kreis, das Diagramm oder das Browserfenster verlässt
- Bei Maus-Events musste man für denselben Effekt Event-Handler an
documentregistrieren; bei Pointer Events istsetPointerCapture(event.pointerId)einfacher - Ohne Pointer Capture kann der Drag stoppen, wenn man schnell zieht oder das Diagramm verlässt und wieder zurückkehrt
- Auch wenn die Taste außerhalb des Diagramms losgelassen wird oder man mit Alt+Tab in ein anderes Fenster wechselt, hängt die Behandlung des Drag-Endes davon ab, ob Capture aktiv ist
- Auf Touch-Geräten ist Pointer Capture das Standardverhalten, bei der Maus muss es jedoch explizit gesetzt werden
Touch-Scrolling und preventDefault()
- Auf Touch-Geräten löst ein Drag mit einem Finger standardmäßig Seitenscrolling aus
- Wenn derselbe Ein-Finger-Drag auch den Kreis bewegt, treten Scrolling und Objekt-Dragging gleichzeitig auf
- Die einfachste Lösung ist, auf das Diagramm per CSS
touch-action: noneanzuwenden; dann kann jedoch nirgends im Diagramm gescrollt werden - Gewünscht ist ein Verhalten, bei dem Scrolling nur beim Ziehen des Kreises verhindert wird, während Scrollen beim Ziehen des Diagramms selbst erlaubt bleibt
- Das Vergleichsergebnis lautet wie folgt
touch-action: noneauf dem gesamten Diagramm: Sowohl Kreis-Drag als auch Diagramm-Drag stoppen das Scrolling- Standardwert: Keines von beiden verhindert Scrolling
touch-action: nonenur auf dem Kreis: Funktioniert nicht wie erwartetpreventDefault()intouchstart: Verhindert nur während des Kreis-Drags das Scrolling und erlaubt das Scrollen des Diagramms
preventDefault()wird antouchstartgehängt, nicht anpointerstart- Dieser Ansatz funktioniert in den meisten Situationen, jedoch nicht im „mobile emulation mode“ von Desktop-Firefox; im mobilen Firefox auf Android funktioniert er
Start-Offset beim Draggen speichern
- Wenn beim Ziehen am Rand eines Objekts dessen Mittelpunkt sofort zur Pointer-Position springt, fühlt sich die Bedienung schlecht an
- Die Lösung besteht darin, beim Start des Draggens die relative Position zwischen Objektmittelpunkt und Pointer-Position zu speichern
- Bei anschließenden Bewegungen wird die Objektposition berechnet, indem die gespeicherten Werte
dxunddyzu den aktuellen Pointer-Koordinaten addiert werden - Dadurch wird
state.draggingnicht mehr als einfachestrue/falsegeführt, sondern als relative Positionsinformation wie{dx, dy} - Bei der Maus ist der Sprung beim Greifen am Rand deutlich sichtbar, bei Touch kann er weniger auffallen, weil der Finger die Stelle verdeckt
- Ein verwandtes kleines Detail ist auch auf der Seite little details beschrieben
Kontextmenü und Maustasten
- Wie das Kontextmenü aktiviert wird, unterscheidet sich je nach Plattform
- Windows: Rechtsklick down+up, Shift+F10
- Linux: rechte Taste down, Shift+F10
- Mac: rechte Taste down, Ctrl+Linksklick
- iOS: Long Press nur auf Text
- Android: Long Press überall
- Das Problem ist, dass
pointerdownsichtbar ist,pointerupaber nicht immer - Die Taste kann tatsächlich bereits losgelassen sein, während der Code sie noch als gedrückt interpretiert
- Die Gegenmaßnahme besteht darin, die rechte Taste zu ignorieren und den Zustand
draggingnur bei der linken Maustaste zu setzen - In der Pointer-Events-Spezifikation bedeutet beim Attribut
buttonder Wertbutton = 0die primary button; damit werden mittlere und rechte Taste ausgeschlossen - In Chrome und Safari auf dem Mac kann Ctrl+Klick wie ein
pointerdownder linken Taste ankommen, daher muss auchevent.ctrlKeygeprüft werden - Die Optionen sind folgende
- Mit
preventDefault()incontextmenulässt sich das Menü unterdrücken, dies kann aber mit Standardaktionen für Mittelklick oder Dragging kollidieren - Wenn nur mit der linken Taste gezogen wird, lassen sich die meisten Unterschiede beim Rechtsklick vermeiden
- Wer auch Mac Ctrl+Klick vermeiden will, muss zusätzlich
ctrlKeyausschließen
- Mit
- Selbst wenn Änderungen der Tasten nicht über
pointerdownoderpointerupübertragen werden, lassen sie sich teilweise über den Tastenzustand inpointermoveprüfen
Objekte mit Text und Bildern
- Wenn ein ziehbares Element Text oder Bilder enthält, ist zusätzliche Behandlung nötig
- Während ein Kreis gezogen wird, kann der darin enthaltene Text ausgewählt werden
- Die Lösung besteht darin, per CSS
user-select: noneanzuwenden- Wird es immer angewendet, ist der darin enthaltene Text nie auswählbar
- Wird es nur während des Draggens angewendet, bleibt die Textauswahl im Normalzustand möglich
- Stand Anfang 2023 unterstützt Safari
user-selectohne Präfix noch nicht, daher muss auchwebkitUserSelectgesetzt werden - Windows, Linux und Mac unterstützen Drag and Drop von Text und Bildern zwischen Anwendungen, was mit dem Ziehen von Objekten kollidiert
- Wenn in
dragstartpreventDefault()aufgerufen wird, kann das Objekt-Dragging Vorrang vor dem Ziehen von ausgewähltem Text oder Bildern erhalten - Je nach Browser und Betriebssystem unterscheidet sich das Verhalten leicht
Mehrere Pointer und gleichzeitiges Draggen
- Ob sich mit mehreren Fingern oder mehreren Mäusen mehrere Objekte gleichzeitig ziehen lassen, ist ein Edge Case
- Bei Touch funktioniert der gezeigte Code bereits zum gleichzeitigen Ziehen mehrerer Objekte
- Wenn zwei Finger dasselbe Objekt ziehen, ist eine separate Behandlung nötig
- Die Lösung besteht darin, in
pointerdownevent.pointerIdinstate.draggingoder im Zustand zu speichern und inpointermovealles zu ignorieren, was nicht dieselbepointerIdhat - Dieses Implementierungsbeispiel ist im Haupttext nicht vollständig angewendet, kann aber im canvas dragging test nachvollzogen werden
- Für Mäuse hält die Pointer-Events-Spezifikation fest, dass normale Betriebssysteme und User Agents in der Regel kein Konzept mehrerer Mauseingaben haben
- Selbst wenn Trackpad und externe Maus gleichzeitig vorhanden sind, werden sie normalerweise als ein Mauszeiger behandelt; daher wird davon ausgegangen, dass es keine Möglichkeit gibt, mit unterschiedlichen Mäusen unterschiedliche Objekte zu ziehen
Wenn mehrere Maustasten gleichzeitig gedrückt werden
- Werden mehrere Maustasten gleichzeitig gedrückt, unterscheidet sich das Verhalten von Pointer Events und Mouse Events
- Mouse Events senden bei jedem Tastendruck
mousedownund bei jedem Loslassen einer Tastemouseup - Laut Pointer-Events-Spezifikation wird
pointerdownbei der zuerst gedrückten Taste undpointerupbei der zuletzt losgelassenen Taste ausgelöst - Dadurch können
pointerdownundpointerupbei unterschiedlichen Tastenzuständen auftreten - Wenn man zum Beispiel die linke Taste drückt, dann die rechte Taste drückt und anschließend die linke Taste loslässt, kann das Objekt weiter im Dragging-Zustand bleiben
- Die Lösung besteht darin, in
pointermovemitevent.buttons & 1den Zustand der linken Taste zu prüfen undend(event)aufzurufen, wenn sie nicht mehr gedrückt ist - Pointer Capture kann bestehen bleiben, bis alle Tasten losgelassen wurden, und ohne explizite Freigabe der Capture weiterlaufen
- Diese Implementierung behandelt nicht dieses Problem und alle anderen Edge Cases vollständig
Verschachtelte Drag-Elemente
- Wenn sich in einem ziehbaren Element ein weiteres ziehbares Element befindet, verarbeiten sowohl das innere als auch das äußere Element das Dragging
- Die Lösung besteht darin, im inneren Drag-Element
event.stopPropagation()aufzurufen, damit das Event nicht zum äußeren Drag-Element aufsteigt - Dabei wird die Weitergabe sowohl in
pointerdownals auch inpointermovegestoppt - Eine Demo ist im Haupttext nicht enthalten, aber im Vue-playground-Beispiel ist ein Fall zu sehen, in dem das rote Drag-Element ein Kind des gelben Drag-Elements ist
Drag in Canvas
- Normalerweise verwendet man SVG, aber in
<canvas>lassen sich keine Event-Handler oder Mauszeiger-Formen direkt auf den ziehbaren Elementen selbst setzen - Bei 2D Canvas oder WebGL muss man Event-Handler an
<canvas>hängen und selbst prüfen, ob die Koordinaten über einem ziehbaren Objekt liegen - Die Verarbeitung läuft wie folgt ab
pointerdown,touchstart,dragstart: sofort zurückkehren, wenn der Pointer nicht über einem ziehbaren Objekt liegtpointermove: den Cursor abhängig davon setzen, ob der Pointer über einem ziehbaren Objekt liegt
- Die Demo befindet sich auf der examples page
Kombination aus Maus-Hover und Touch-Drag
- Manche Diagramme reagieren bei der Maus auf Hover ohne gedrückte Taste; auf Touch-Geräten gibt es kein Hover, daher müssen sie auf Drag reagieren
- Das Grundrezept erwartet sowohl bei Maus als auch bei Touch einen Drag
- Um Maus-Hover und Touch-Drag gemeinsam zu verwenden, entfernt man in
pointermovedie Bedingungif (!state.dragging) - Auf der Responsive Design page ändert sich das Layout, wenn man mit der Maus über eine Zeile hovert; bei Touch ändert sich das Layout per Drag
- Auch im Hexagons Guide verwenden viele Diagramme Maus-Hover und Touch-Drag gemeinsam
- Wenn beim Ziehen auf Touch von Element A zu B die Capture erhalten bleibt, werden Move-Events an A zugestellt; um B hervorzuheben, kann es daher nötig sein, die Capture freizugeben
Toggle Paint und Erweiterung des Drag-Zustands
- Beim Rounded Cell Painter erfasst
pointerdowndie anfängliche Malfarbe - Danach verwenden alle
pointermove-Events bispointerupdieselbe Malfarbe - Dafür muss
state.draggingneben dem anfänglichenxundyauch die anfängliche Malfarbe enthalten - Die Demo befindet sich auf der examples page
Edge Case bei lostpointercapture
lostpointercapturekann verwendet werden, um zu erkennen, ob die Pointer-Capture verloren gegangen ist- In welchen Situationen das auftritt und was man dann tun sollte, ist noch nicht vollständig geklärt
- Das in den Kommentaren behandelte Szenario sieht so aus
- Drag eines Kreises mit der linken Taste starten
- Mit gedrückter linker Taste aus dem Drag-Bereich heraus bewegen
- Die rechte Taste drücken und anschließend die linke Taste loslassen
- Irgendwo außerhalb des Drag-Bereichs klicken
- Der standard besagt, dass der User Agent nach Freigabe der Pointer-Capture zwingend ein
lostpointercapture-Event auslösen muss - Das tatsächliche Browser-Verhalten unterscheidet sich stark: Auf manchen Systemen tritt
lostpointercaptureauf, auf anderen erst, wenn an anderer Stelle geklickt wird
1 Kommentare
Meinungen auf Hacker News
Ein Artikel über Dragging; ich bin auf fast alle Fallstricke gestoßen, die Amit nennt, und bei denselben Lösungen gelandet. Hervorragender Artikel.
Aus Nutzersicht wirkt Reihenfolge per Drag-and-drop ändern völlig selbstverständlich, aber sobald man sich mit unterschiedlich großen Elementen, Platzhaltern zwischen Elementen, Kantenerkennung und dem Kaninchenbau von Box-Fitting-Algorithmen beschäftigt, gehört das schon zu den ziemlich schwierigen Dingen, die man selbst implementieren kann.
Bei jeder Mausbewegung wählt man aus diesen Positionen diejenige aus, die der aktuellen Position des gezogenen Objekts am nächsten liegt, und rendert das ausgewählte Layout. Das funktioniert auch bei komplexen Layouts oder Reflows mit gutem Gefühl.
Am Ende habe ich das Layout so berechnet, als würde die Karte beim Aufheben vom Board gelöscht; dadurch wurde es einfacher. Man muss dann nur noch die Dinge wegschieben, die sich an der Stelle befinden, über der die Maus ist.
Trotzdem gab es enorm viele Sonderfälle, vor allem wenn man begann, eine neue Karte zu bearbeiten, die noch nicht ins Datenmodell „committed“ war. Ich musste auch eine Option hinzufügen, vorhandene Karten wegzuschieben, um Platz für eine Geisterkarte zu schaffen.
Es hilft, anzuerkennen, dass es viele Sonderfälle gibt, die man manuell behandeln muss. Wenn man mit der Annahme herangeht, es müsse eine allgemeinere, homogenere Lösung geben, dreht man sich im Design im Kreis. Irgendwann sollte ich die verschiedenen Sonderfälle wohl in einem Blogpost zusammenfassen.
Ich habe auch eine bessere Layout-Methode gefunden, die unnötige Berechnungen reduziert und die Layout-Engine flexibler und nutzerfreundlicher macht. Es ist erstaunlich, wie stark die Wahl der Datenmodell-Repräsentation die Lösung beeinflusst.
[1] https://www.ussherpress.com/cardbuddy/
Der Artikel scheint Abbruchbehandlung nicht zu behandeln. Unter Windows gibt es zum Beispiel die Konvention, dass ein Drag-Vorgang mit Escape abgebrochen wird.
Manchmal möchte man einen Drag-Vorgang auch abbrechen, wenn die Maus außerhalb eines definierten Bereichs losgelassen wird. Abbrechen bietet Nutzern ein schnelleres Undo oder einen Ausweg nach dem Motto: „Ach, eigentlich wollte ich gar nicht ziehen.“
Daher sollte man selbst dann, wenn es keine separate Undo-Funktion gibt, den ursprünglichen Zustand zum Zeitpunkt des Drag-Starts speichern und bei einem Abbruch wiederherstellen.
Beim Abbruch durch Ziehen außerhalb des Bereichs gibt es außerdem eine Abbruch-Aufhebung: Solange sich der Pointer außerhalb des Bereichs befindet, zeigt man visuell an, als sei der ursprüngliche Zustand wiederhergestellt; wenn der Pointer wieder in den Bereich zurückkehrt, wird der Drag-Vorgang fortgesetzt. Der eigentliche Abbruch passiert erst beim Loslassen der Maus, aber schon während des Ziehens wird visuell vorweggenommen, dass abgebrochen würde.
Ich kann kaum glauben, dass Microsoft das immer noch für gutes UI-Design hält. Es verwirrt Nutzer völlig; man sieht Leute, die nicht wissen, was passiert, Angst haben, die linke Maustaste loszulassen, oder das Ganze wie ein Jo-Jo wiederholen.
Ich stimme zu, dass Abbrechen ein großes, oft übersehenes Problem ist und nicht trivial. Normale Nutzer wissen nicht einmal, dass ESC helfen kann.
Aus 20 Jahren Erfahrung damit, in der GUI von Ardour [0] alle möglichen Objekte zu ziehen, habe ich ein weiteres Detail gelernt: Jedes Button-Down- und Button-Up-Event sollte als Drag-Event ohne Bewegung behandelt werden.
Das heißt: Ein Down startet immer einen Drag, ein Up beendet ihn, und der Code, der das Up verarbeitet, behandelt die Bedingung „keine Bewegung“ als Sonderfall.
[0] https://ardour.org/
Ich habe normale Handler für
mousedownundmouseupund nutze ein konfigurierbares Timeout von etwa 150 ms, um zu entscheiden, ob es ein Klick, der Beginn eines Drags oder eine andere Aktion ist. Wennmouseupausgelöst wird oder die Maus den Bildschirm verlassen hat und ein Drag läuft, mache ichstopdrag. Für mich ist Dragging nur einer von mehreren Sonderfällen.Vor sehr langer Zeit gab es in Minneapolis ein von der NSF gefördertes Geometry Center. Dort fand eine Tagung zur computergestützten Gruppentheorie statt, und man hatte ein paar maskottchenartige Leute aus angrenzenden Bereichen eingeladen.
Ich wurde eingeladen, weil ich ein System für algebraische Geometrie geschrieben hatte, stand bei -20 °F sehr früh auf, sicherte mir die Silicon-Graphics-Workstation, die ich an diesem Tag benutzen würde, und programmierte ein Spiel, um Gruppengeneratoren und Relationen besser zu verstehen.
In diesem Spiel gab es Dragging. Daraus entstanden gute Gespräche bis 2 Uhr morgens. Meine Ansicht war, dass Dragging nicht den physikalischen Gesetzen der realen Welt folgen muss; Dragging sollte sich anfühlen wie ein sehr starker LSD-Tab. Alle stiegen darauf ein und brachten ihre eigenen Ideen ein.
Der Artikel implementiert, wenn man die vielen Ausnahmen im Webbrowser einmal beiseitelässt, ein relativ grundlegendes Dragging. Ich frage mich, ob es Material zu Dragging mit Einschränkungen gibt, etwa Einrasten an Leitlinien, Verhindern von Kollisionen mit Begrenzungen oder anderen Objekten, oder animierte Drop-Ziele, deren Größe oder Position sich als Reaktion auf den Drag-Vorgang ändert.
Früher wollte ich einmal eine angepasste Pomodoro-Timer-UI bauen, bei der eine runde Uhr in keilförmige Stücke unterschiedlicher Länge unterteilt wird, um Fokus-/Pausenintervalle zu definieren. Bei der Umsetzung des Umordnens dieser Keile per Drag kam ich nicht besonders weit.
Ich wollte das Constraint-System vom Event-Handling-System trennen. Bibliotheken wie jquery-ui koppeln beides, sodass das Event-Handling-System alle möglichen Constraints kennen muss. jquery-ui unterstützt Bounding Boxes, Achsen, quadratische Raster und das Einrasten an DOM-Elementen. Wenn man aber an ein hexagonales Raster, ein logarithmisch skaliertes Raster oder eine kreisförmige Begrenzung einrasten will, wird das nicht unterstützt.
Im Code sieht man
state.pos = …; dort wird der Zustand gesetzt. Für Constraints würde ichposhinter einem Setter verstecken. Dann kann der Setter die Constraints anwenden, ohne dass der Code zur Verarbeitung der Drag-Events wissen muss, welche Constraints benötigt werden.Ich sollte die Seite aktualisieren und ein Constraint-Beispiel zeigen. Ich habe völlig vergessen, diesen Aspekt im Code-Rezept zu erwähnen.
Ein älteres Constraint-Beispiel gibt es unter https://www.redblobgames.com/articles/curved-paths/making-of..., und ein Beispiel zur Kollisionsvermeidung unter https://redblobgames.github.io/circular-obstacle-pathfinding.... Umordnen per Drag oder animierte Drop-Ziele habe ich allerdings noch nicht ausprobiert.
Dieser Website bin auch ich dankbar. Als wir bei Uber ein Hexagon-System für Geoanalysen entwarfen, habe ich mich stark auf https://www.redblobgames.com/grids/hexagons/ gestützt.
Ein guter Artikel, der die Fallstricke und Dinge, auf die man achten muss, wenn man echte Interaktionen umsetzt, gut zusammenfasst.
Für etwas, das eher „sofort einsatzbereit“ ist, habe ich in mehreren Projekten über ziemlich lange Zeit interactjs verwendet.
Danke an alle. Ich war überrascht, es heute auf HN zu sehen, und kann Fragen beantworten.
draggable="true"und Drag-Events angesehen hast.Mich würde auch interessieren, wo die eingebaute Funktionalität im Vergleich zu diesem Code zu kurz kommt.
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement...
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement...
https://developer.mozilla.org/en-US/docs/Web/HTML/Global_att...
Ich habe eine kurze Frage. Wie kann man mit eingebauten DOM-Events wie
dragstartdie Drag-Bewegung auf eine Achse beschränken? Ich habe Drag-and-Drop-Funktionalität mit den Eventsdragstart/dragenter/dragover/dropimplementiert, aber keinen schnellen Weg gefunden, die Bewegung auf die x-Achse zu beschränken. Ich meine mich zu erinnern, dass Javascripts Drag-and-Drop-API von jQuery das unterstützt hat. Ich möchte nur native DOM-Events/APIs verwenden, daher wären entsprechende Informationen oder Hinweise hilfreich.Angesichts der Tatsache, dass Safari erst 2020 Unterstützung hinzugefügt hat, frage ich mich, ob du dir keine Sorgen machst, dass Browser sie nicht unterstützen. Nutzer von macOS Mojave dürften Safari 13 oder 14 verwenden, also hoffe ich, dass Safari 12 in der Praxis nicht mehr genutzt wird; aber wenn in einer Produktions-App für normale Webnutzer eine so einfache Funktion wie Dragging nicht funktioniert, wäre das ziemlich schlecht.
Während des Draggings Event-Handler auf
documenthinzuzufügen, ist eine alte Praxis, und Browser fügen ständig neue Funktionen hinzu, um bestimmte Anwendungsfälle zu vereinfachen, aber jede davon hat ihre eigenen Ausnahmen und Fallstricke. Auch der Artikel sagt, dass er das noch nicht vollständig behandelt. Im Endergebnis bleibt sogar eine Kombination aus Pointer Events und Touch Events übrig.Ich frage mich, ob „Einfachheit“ im Sinne von weniger Code es wert ist, dafür zusätzliche Ausnahmen, geringere Browser-Unterstützung und den Aufwand in Kauf zu nehmen, dass Entwickler Details und Browser-Unterschiede von Pointer Events kennen müssen, die vermutlich weniger gut verstanden und dokumentiert sind als Mouse Events.
Eines fehlt noch: Barrierefreiheit. Wie sollte man Dragging per Tastaturbedienung ermöglichen?
In meinem Beispiel wäre die Antwort für Barrierefreiheit, zusätzlich eine Tastaturbedienung bereitzustellen, mit der sich die Aufgabe erledigen lässt. Wenn möglich, ohne dass man eine Taste dauerhaft gedrückt halten muss. Zum Beispiel könnte man per Tastenkürzel in einen Panning-Modus wechseln und dann mit den Pfeiltasten den Viewport über die Leinwand bewegen; damit wäre das Problem gelöst.
Bei ziehbaren Objekten ist die Aufgabe das Ändern der Reihenfolge. Um das barrierefrei zu machen, stellt man eine alternative Möglichkeit bereit, die Reihenfolge zu ändern. Zum Beispiel kann man mit TAB den Fokus zwischen den Objekten durchschalten, mit einer Taste das fokussierte Objekt auswählen und dann mit den Pfeiltasten die Drag-Vorschau zur nächstgelegenen gültigen Position in der jeweiligen Richtung verschieben.
Das heißt: Tastaturunterstützung nicht mit Gewalt in die Drag-Implementierung hineinzwängen, sondern zusätzlich zur Drag-Funktion eine sinnvolle Tastaturfunktion bereitstellen.
Gut. Anfang der Woche habe ich in einer Vue-App SVG-Element-Dragging implementiert, und ich musste fast alles, was der Autor beschreibt, in derselben Reihenfolge selbst herausfinden; am Ende ist es auch fast dieselbe Komponente geworden.
Allerdings habe ich es statt als funktionale Komponente als composable Utility namens
useDragginggeschrieben.