Beej's Bit Bucket

 ⚡ Tech and Programming Fun

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:

  1. If the mousedown happens on the draggable, put it into "drag mode". Additionally, remember which item is in "drag mode".
  2. If the mouse is moved, and an item is in "drag mode" move that item to where the mouse is.
  3. 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:

License

The code attached to this article is licensed under the MIT open source license.

Comments

Blog  ⚡  Email beej@beej.us  ⚡  Home page