2012-03-15
Drag and Drop Demo
This is a demo of how to roll-your-own Drag and Drop system using JavaScript and CSS, mostly just to clarify how such systems might function. Of course, there are prebuilt ones out there—modern HTML even supports it out of the box—but don't you want to know how they work? 😀
Here are a couple quick links to supporting files: JavaScript library (look here to see the guts of the drag-and-drop library), and the JavaScript usage for this page (look here to see how to use the library).
This demo makes use of jQuery to make our lives easier, but the drag-and-drop functionality is, of course, hand-rolled.
The basic idea is that we're moving the element to whereever the mouse
is. We set the draggable element's position
CSS property to
absolute
, so that we can position it out of flow at will. And then we
set the X coordinate with the left
CSS property, and its Y
coordinate with the top
CSS property. These coordinates are relative
to the draggable's enclosing element (in this example, it's the big
green field.)
The plan of attack is this:
- If the mousedown happens on the draggable, put it into "drag mode". Additionally, remember which item is in "drag mode".
- If the mouse is moved, and an item is in "drag mode" move that item to where the mouse is.
- If mouseup happens, put the item out of drag mode, and forget that we are dragging it. Also, compare its position to any droptarget objects—if there's a collision, notify the droptarget.
Drag Details
Earlier I said that we wanted to position the draggable element to
whereever the mouse is, it was a bit of an oversimplification for usual
usage. It has to do with the origin of the draggable object, relative
to where it is grabbed with the mouse. Since the origin (coordinate
0,0
) on the goats, above, is in the upper left of the image, if you
simply move the image to where the mouse is, the image's upper left
corner will always be pinned to the mouse.
That sort of works, but what would be better would be to remember the relative position of the mouse upon the image, and then use that to keep the image in the same place relative to the mouse when it is dragged.
For example, if we click on coordinates (10,20) on the image, that means the image must be positioned on the page 10 pixels left of the mouse, and 20 pixels above the mouse.
So what we should do is remember the position of the mouse on the draggable when it is first clicked. And then each time the mouse is moved during the drag, we position the image at the mouse coordinates minus the position of the mouse on the image when it was first clicked.
Now, from a coding standpoint, the situation gets even further complicated. The only sane way to get mouse coordinates cross-browser is to find them relative to the entire document. However, the absolutely-positioned draggables are relative to their containing element. In order to reconcile this, this library converts the mouse coordinates from document page into the space of the containing element. Then when we set the position of the draggable, it is positioned correctly in its container element.
Finally, if you noticed in the demo, above, the white goats are
restricted to the grass, but the magical blue goat use their secret
hidden power to have free rein of the entire document. This secret
power is this: if the goat has both classes draggable
and
stayinparent
, it will be restricted to its parent element. It's just
a little bonus feature I added to the library. See the beejdnd.drag()
function in the dd.js
library for details.
Drop Details
A drop container is marked with the class droptarget
. Each time a
draggable is dropped, the drag-and-drop library searches through the
elements with that class, and tries to find collisions. If the mouse is
over a droptarget when the draggable is dropped, an event ('drop'
) is
sent to the droptarget with the second parameter to the event handler
being the dropped DOM element.
Usage
The upshot of all this is that we can set the elements that are
draggable to have class="draggable"
, and we can set the elements that
are droptargets to have class="droptarget"
.
<img src="goat.jpg" class="draggable" id="goat0">
<div class="droptarget" id="droptarget0"></div>
And then we can call beejdnd.init()
in the listing below at MARK 1
which sets up the event handlers properly.
You'll also need an event handler for the droptarget. As usual, the
first parameter is the event, and we've added a second parameter at
MARK 2
that references the draggable element—that's what you use
to tell them apart when the item is dropped.
Finally, you can just listen for the 'drop'
event, as shown at MARK 3
.
// init the DnD system:
beejdnd.init(); // [MARK 1]
// callback function for the droptarget 'drop' event:
function demodrop(e, draggable) { // [MARK 2]
alert(draggable.id + " was just dropped on " + e.target.id);
}
// set up the droptarget event handlers:
$('#dropzone0').bind('drop', demodrop); // [MARK 3]
Further Development
Since this is just a demonstration for learning purposes, there are several improvements that can (and some that should) be made.
Here are some suggestions for further improvements:
-
Make sure the goat doesn't get lost off-screen (notably off the left or top).
-
Limit the goat's travel to a certain box.
-
Limit the goat's travel to a certain axis.
-
See how limiting the goat's travel to a box and axis is just like making a scrollbar. 😀
-
Make it so that if any part of the draggable falls on the droptarget (not just if the mouse pointer is over the droptarget), the event triggers.
-
Sometimes you might want to create another temporary draggable item that only exists for the life of the drag. This could be a clone of the original object, or another element that looks different (like a ghosted version of the original with a big "+" sign next to it.
-
Cause the display to scroll when the draggable object is moved past the edge of the window.
-
Could you use relative positioning to reposition elements "in flow" in the document? The drag-n-drop elements in this demo deliberately use absolute position to keep the objects "out of flow".
-
Modify the system to support multiple simultaneous draggables on multitouch devices.
-
The library uses jQuery considerably; it would make sense to convert it to a jQuery plugin.
License
The code attached to this article is licensed under the MIT open source license.