In which I show how to harness jQuery UI’s Mouse plugin to roll your own drag-and-drop handling, when Draggable is not flexible enough for you.
Overview
Sometimes you need tighter control over drag-and-drop logic than jQuery UI’s Draggable and Droppable plugins afford. For instance, when I wrote up the Solitr game, I initially used Draggable, but I ended up with an unmaintainable mess of auxiliary “drop-zone” divs, and I also didn’t find the drop logic to be flexible enough for a game.
But simply binding to mousedown and mousemove events yourself will cause a headache because you’d have to work around subtle cross-browser compatibility issues.
Luckily, jQuery UI comes with a Mouse plugin. (Incidentally, Draggable derives from this.) We can use this to handle mouseStart, mouseDrag, and mouseStop events in a way that works consistently across browsers.
Setup
It’s not possible/useful to instantiate the mouse
widget directly, but we can
easily subclass it to make it usable with our own custom event handlers. Simply
copy and paste the following code, which registers a custommouse
plugin, to
get started:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Now instantiate the custommouse
plugin we just defined, and pass your own
event handlers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Event Sequence
Say the user starts dragging horizontally at point 50, 50
, with distance
set to 10
. Then the event sequence is guaranteed to be as follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
So much for the theory. Let me give you some practical hints on how to implement this:
Practical Hints
There are many coordinate properties on the event object, but you should use
e.pageX
and e.pageY
, which are
standardized
by jQuery to return the coordinates relative to the top left corner of the
entire document.
The only exception is the
elementFromPoint
method, which on modern
browsers takes
e.clientX
and e.clientY
and returns the element under that point.
1 2 3 4 5 |
|
Then in the mouseDrag
handler, calculate the offset:
1 2 3 4 5 6 7 8 9 10 11 |
|
Finally, in mouseStop
, snap the element to the nearest drop point (or
whatever
logic
you want to implement), and update the application state if necessary.
Finally
It would be sweet to handle touch events to make this work on mobile devices. Unfortunately, the Mouse plugin doesn’t support touch handling yet. I have a feeling that there will a lot of issues with inconsistent browser behavior if I try to do this myself, so I’m leaving it for now.
In any case, I hope that this post was helpful to you. If you have practical insights or alternative techniques to share (perhaps even without using jquery.ui.mouse), please leave a comment!