top | item 37703291

Draggable objects

1059 points| stefankuehnel | 2 years ago |redblobgames.com | reply

141 comments

order
[+] wildrhythms|2 years ago|reply
This article is about dragging, and I've run into all of the pitfalls and come to the same solutions that Amit talks about. Excellent article!

One of the hardest things I have needed to code from scratch is drag-to-reorder. It seem so natural from a user perspective, but when you get into inconsistently sized items, having to create placeholders between items, detecting edges, going down rabbitholes of box-fitting algorithms... it's a fun challenge :)

[+] aboodman|2 years ago|reply
I have a trick for this that I love that is very general:

1. When a user begins dragging, calculate the layouts for all possible drop targets (or perhaps just those that are currently visible). 2. For each of those layouts record the position the dragged objects ends up in. 3. On each mouse movement, select from those positions the one that's closest to the dragged object's current position 4. Render the selected layout

This ends up feeling really good and works for any kind of complex layout / reflow.

[+] allenu|2 years ago|reply
I had to tackle this problem for my index card app, Card Buddy. [1] It was definitely a fun challenge and I still found a better way to do it later.

What I ended up doing is when you pick up a card, I compute the layout as if the card was deleted from the board, and then it becomes easy. Wherever you hover the mouse, I just displace whatever is there.

There were still tons of edge cases I had to work out, though, especially when you start editing a new card that hasn’t been “committed” to the data model yet. I had to add the option to shift existing cards out of the way to make room for a phantom card.

It helps to recognize that there are just lots of edge cases you have to manually handle. If you try to tackle it as though there’s a more generic/homogeneous solution, you end up going around in circles a bit with the design. I should probably create a blog post on all the different edge cases.

As I said, though, I found an even better way to do my layout, which saves on unnecessary computations and makes the layout engine more flexible and user-friendly. (It’s amazing what a difference your choice of data model representation makes on your solution.) It’s been a fun puzzle to solve!

[1] https://www.ussherpress.com/cardbuddy/

[+] pupppet|2 years ago|reply
Oof, or drag-to-reorder while supporting nesting.
[+] slowhadoken|2 years ago|reply
Same. It’s such a simple idea but execution can be brutal. I also took drag select for granted for years too.
[+] layer8|2 years ago|reply
The article doesn’t seem to discuss cancellation. For example, there is the convention (at least on Windows) that pressing Escape cancels the dragging. Sometimes you also want to cancel the dragging when the mouse-up happens outside of some defined area. Cancellation serves as a quicker Undo (or an “oh, I actuality didn’t mean to drag”) for the user. In any case, this means that you have to save the original state at the start of the dragging, so that it can be restored if the dragging is cancelled, even if you otherwise provide no Undo functionality.

In the case of cancellation-when-dragging-outside-an-area, there’s also un-cancellation, meaning you resume the dragging when the pointer returns to the area, after the state visually reverted to the original one while outside the area (to indicate to the user that a cancellation would happen if the mouse button is released at that point). Or put differently, the real cancellation only happens upon mouse-up, but is already visually indicated while dragging.

[+] curiousObject|2 years ago|reply
>Sometimes you also want to cancel the dragging … outside of some defined area.

But that Windows <<SNAP>> when you drag something too far so the dragged object and the pointer just suddenly disappears, and snaps back to the original position without warning!

Can’t believe Microsoft still believes that is smart UI design. It utterly confuses users. You may even see them caught in a yo-yo situation or just scared to release the left mouse button while they don’t know what’s happening

Cancellation is a huge often overlooked issue - I agree with you and it’s not a simple issue. Common users don’t know that ESC can help.

[+] qingcharles|2 years ago|reply
Is there a different cancelation key used on other platforms, other than ESC that I should handle?
[+] PaulDavisThe1st|2 years ago|reply
One extra detail, something I've learned from 20 years of working on dragging all kinds of objects around the GUI of Ardour [0]: handle ALL button press and release events as drag events where there is no movement. That is: press ALWAYS starts a drag that is finished by the release, and the code that handles release "special cases" the no-movement condition.

[0] https://ardour.org/

[+] hutzlibu|2 years ago|reply
That should also work, but I did not do it this way and had no problems after I got the basics right. I have general mousedown and mouseup handlers and use a timeout(~150 ms, but configurable), to determine if we deal with a click, or start dragging (or other things). And on mouseup (or if the mouse leaves the screen) and there is dragging in progress -> stopdrag. So dragging for me ist just one of different special cases ..
[+] wruza|2 years ago|reply
What’s the rationale here?
[+] Syzygies|2 years ago|reply
In the distant past, there was this NSF-funded geometry center in Minneapolis. There was a computational group theory conference (these guys are over the top) and they invited a few mascots from neighboring fields. I had written a system for algebraic geometry, and got an invite. I'd get up really early in the -20F cold to insure a Silicon Graphics workstation for the day, and set about coding a game to better understand group generators and relations.

It involved dragging.

I loved the 2am conversations that resulted. My idea was that dragging need not respect real-world physics. Dragging should feel like a great tab of acid. And everyone was into this, everyone had ideas.

[+] rgovostes|2 years ago|reply
The article implements relatively basic dragging (notwithstanding the several edge cases that arise with web browsers). Are there resources on dragging with constraints such as snapping to guidelines, preventing collisions with boundaries or other objects, or animated drop targets that resize or move in response to the drag operation?

I once wanted to make a customizable Pomodoro timer UI based on subdividing a circular clock into wedges of different durations to define your focus/break intervals. I didn't get very far trying to implement drag-to-reorder of the wedges.

[+] amitp|2 years ago|reply
The way this code works, the drag motion updates some state. I can apply constraints when setting the state. Then the state drives the redisplay.

I wanted to separate the constraint system from the event handling system. Libraries like jquery-ui tie them together, so the event handling system has to know all the possible constraints. In jquery-ui, they support bounding box, axis, square grid, snapping to dom elements. But what if I wanted snapping to a hex grid, or a logarithmic scale grid, or a bounding circle? It's not supported.

In the code you'll see "state.pos = …". That's where the state is set. For constraints, I put "pos" behind a setter. Then the setter can apply the constraint, without the drag event handling code having to know what type of constraints are needed.

I should update the page to show some examples of constraints. I completely forgot to mention this aspect of the code recipe. (Thanks!)

I have some older examples of constraints at https://www.redblobgames.com/articles/curved-paths/making-of... and a prevent-collision example at https://redblobgames.github.io/circular-obstacle-pathfinding... . However I haven't tried drag-to-reorder or animated drop targets.

[+] qiller|2 years ago|reply
Great write up for all the pitfalls and gotchas that come up when dealing with proper interactions

For something more "out of the box", I've been using interactjs for quite a while for a variety of my projects

[+] amitp|2 years ago|reply
Thank you everyone! What a surprise to be on HN today. Happy to answer questions!
[+] ww520|2 years ago|reply
That is a fantastic discussion on dragging with Javascript. Really appreciate the information.

I've a quick question. How do you restrict the dragging movement to an axis using the built-in DOM events like dragstart/etc. I had a drag & drop feature implemented using the dragstart/dragenter/dragover/drop/etc events. I couldn't find a quick way to restrict the dragging movement to the x-axis. JQuery's drag and drop API used to support it. I'm trying to use the native DOM events/api only. Any information or pointers are greatly appreciated.

[+] birracerveza|2 years ago|reply
No, thank YOU. This is an awesome resource for learning game algorithms, and it's a lot of fun to just play around with the interactive explanations.

Definitely one of the best websites I know. Cheers!

[+] dgreensp|2 years ago|reply
I used to write browser UI code all the time, but it’s been a few years. This is the first I’ve heard of someone using pointer events.

Do you worry at all about someone’s browser not supporting them, given that Safari added support in 2020? I guess Safari 12 is hopefully no longer used in practice, with macOS Mojave users hopefully running Safari 13 or 14? It would be pretty bad if something as simple as dragging didn’t work in a production app designed for the market of web users at large.

Adding event handlers to the document during a drag is a time-honored practice, and browsers add brand-new features all the time that are intended to simplify some use case or other but have their own edge cases and gotchas, which the article says are not fully addressed. And there’s still a combination of pointer and touch events in the end result. I wonder if the “simplicity” in the sense of less code is worth the additional edge cases, less browser support, and the developer needing to understand the ins and outs and browser differences of pointer events, which are presumably less understood and documented than mouse events.

[+] gaolei8888|2 years ago|reply
Awesome work! Thanks a lot. It is fun to read these type creative articles
[+] liendolucas|2 years ago|reply
Very neat! The way that you precisely get to the point and then snippets are presented and discussed is fantastic. I wish to see more of these kind of detailed explanations everywhere. Code is read and understood effortlessly!
[+] Max_Mustermann|2 years ago|reply
Hey Amit! I remember you and your articles fondly from my RotMG days.
[+] tjfjr|2 years ago|reply
Does redblobgames.com have its own RSS/Atom feed? In its footer there is a link to the feed for your other blog, simblob.blogspot.com. I'd love to keep tabs on both of these blogs in my feed reader.

Big fan of your writing, btw!

[+] dang|2 years ago|reply
Can you please nominate a specific URL on your site that we can change the top link to? (see https://news.ycombinator.com/item?id=37707904 for why)

What's your most interesting piece that hasn't gotten sufficient attention yet? I believe HN has had many great threads about the A* and hexagonal grid articles over the years. Is there a comparable one that's been overlooked so far?

[+] displaynone|2 years ago|reply
One thing missing: accessibility. how ought facilitate drag using keyboard controls?
[+] nkrisc|2 years ago|reply
I think that's a bit off base here. It's like asking how does a keyboard user facilitate click-and-hold to pan the canvas in a drawing program? They don't. Panning with the mouse (or dragging objects) is one interface. The task is panning the canvas, the means to achieve that are varied.

In my example, the answer to the question about accessibility is to additionally provide keyboard controls to complete the task (preferably ones that don't - or optionally don't - require holding a key). For example perhaps a key shortcut to enter a pan mode, in which the arrow keys move the viewport around the canvas. Problem solved.

As for draggable objects, the task is re-ordering. How do you make that accessible? Provide an alternative means to re-order objects, perhaps using TAB to cycle focus through the objects, then a key to select the focused one, and use the arrow keys to move the drag preview to the nearest valid position in that direction.

[+] bastawhiz|2 years ago|reply
Overwhelmingly, the answer is "you don't." Your UI should provide alternatives to dragging that allow folks who can't use a point/touch devices to interact with your page. Which is to say, don't shoehorn keyboard support into your drag implementation, add separate keyboard functionality that makes sense in addition to drag functionality.
[+] stronglikedan|2 years ago|reply
That would be a moveable object, and I reckon it would be easy to implement using this as a basis.
[+] meiraleal|2 years ago|reply
tab / space / enter to select an item, crtl + arrow keys to move the "dragged" element. Obviously, it can only move one block per keystroke.
[+] runarberg|2 years ago|reply
Love this.

I was just implementing dragging an SVG element in a Vue app earlier this week, and had to discover pretty much everything the author describes in the article, even in the same order the author describes them, and ended up with pretty much an identical component to do so (except I wrote a composable utility `useDragging` instead of a functional component `<Draggable>`).

[+] amitp|2 years ago|reply
Cool! I've tried a directive and tried a component (with slots) but I haven't tried a composable, mainly because I wasn't sure how to set up event handlers that way. (I don't have a lot of experience with composables)
[+] phkahler|2 years ago|reply
The page reminded me of Ken Perlins' page:

https://cs.nyu.edu/~perlin/

Yes, the Perlin noise guy among many other things.

[+] bmitc|2 years ago|reply
I always wish there was more specification surrounding his Perlin noise algorithm he has on his website. For example, asking what the range of output values is does not have an easy answer.
[+] npinsker|2 years ago|reply
Off-topic a bit, but I've been curious about a 2D pathfinding problem for a while that this site doesn't seem to tackle despite having lots of articles on the subject. Is there an algorithm out there for finding "enclaves" (i.e. places where you might want to place rewards, spawn the player) within a large 2D terrain grid?

Not super precise, but given a 2D boolean array of pathable/unpathable cells, say generated by Perlin noise, find locations that are only accessible via a relatively narrow "choke point". Example: https://imgur.com/a/jFPXlS5

Standard pathfinding algorithms don't provide enough information to do this, but maybe there's some kind of heuristic approach that could work well.

[+] amitp|2 years ago|reply
Tarjan's Algorithm can be used to find choke points, although I haven't tried it myself [1]. I think what I would try is Breadth First Search with multiple start points to calculate various metrics for the map [2] and maybe All-Pairs if you need more [3]. For example, you might use Tarjan's or All-Pairs to find the most commonly used corridors, and then use Breadth First Search from those "central" points to find the "farthest from central corridor" points.

[1] https://old.reddit.com/r/roguelikedev/comments/dc4orn/identi...

[2] https://www.redblobgames.com/pathfinding/distance-to-any/

[3] https://www.redblobgames.com/pathfinding/all-pairs/

[+] feoren|2 years ago|reply
Apart from the excellent subject matter, I often pull up this site during UI/UX discussions. Amit clearly has the ability to do really advanced JavaScript visualizations, but he only uses it exactly when necessary. Most of it is a plain document like you might write in Markdown, but when he uses JavaScript, it's illuminating, connected to all the other examples, and clean. Any animation he uses is clearly initiated by the user, and is there not because it looks cool, but because the intermediate frames help the user understand what's happening. It also never moves the rest of the layout around. I go back to this site any time I'm pondering how to do good online documentation, interactive help, tutorials, or even text-heavy presentation of results.
[+] mumintrollet|2 years ago|reply
I remember when I got back into programming, this site was one of the things that really made me excited to code + develop a deeper understanding of algorithms :)
[+] slowhadoken|2 years ago|reply
Amit Patel is the man. I always say his last name like Matthew Patel says his name in Scott Pilgrim. Side note: Sebastian Lague is great too.
[+] ChadNauseam|2 years ago|reply
> I always say his last name like Matthew Patel says his name in Scott Pilgrim

One of the seven evil hexes :D

[+] philipov|2 years ago|reply
I think I might call RedBlobGames the #1 website on the internet for learning game algorithms and data structures. Amit is the best!
[+] tsumnia|2 years ago|reply
A fantastic site. When I originally took over teaching Intro to AI, I initially relied on the A* search closed/open set pseudocode explanation[1]. However, when it would come time to ask students to implement it, I was constantly finding students absolutely confused by the approach. Once I swapped over to Amit's A* explanation, the number of confused students dropped significantly. Forever thankful for their walkthrough.

[1] https://en.wikipedia.org/wiki/A*_search_algorithm#Pseudocode

[+] amitp|2 years ago|reply
That's great to hear — thank you!
[+] gowld|2 years ago|reply
What's the difference between the approaches?
[+] ge96|2 years ago|reply
I remember when this was a big deal to me, jQuery days.
[+] michaelwm|2 years ago|reply
Amit was an instrumental part in the development of one of my favorite video games, Realm of the Mad God. It was a masterpiece of the Flash game genre, and its guild feature introduced me to many lifelong friends.