How to Drag and Drop in JavaScript [con't]
You will notice that this basis of this code is mostly taken from our previous examples. Put them all together and you can drag items freely.
When we click on an item we're storing another variable, mouseOffset
.
mouseOffset
simply contains information on where we clicked our item. If we have
a 20x20px image and click in the middle, mouseOffset
should be: {x:10, y:10}. If
we clicked the top-left of our image, mouseOffset
should be: {x:0,
y:0}. We use this information to later position the item under our mouse in the
same way that we clicked on it. If we didn't store this your item would always
be in exactly the same position relative to the mouse regardless of where you
clicked on the item.
Our mouseOffset
function uses another function, getPosition
. getPosition
's
purpose is to return the coordinates of an item relative to the document. If we
simply try to read item.offsetLeft
or item.style.left
we will get the item's
position relative to its parent, not the document. Everything else in our script
is relative to the document so this must be as well.
getPosition
starts with the element we pass it and
loops through each parent of that item adding its left and top positions to a
running total. By doing this we manage to get a top and left total for the item
we want.
Once we have this information and we move the mouse, the mouseMove
function takes over. First we make sure our item style.position
is absolute.
Next we move our item to wheverever the mouse is minus the offset that we
recorded earlier. When the mouse is released dragObject
is set to null and our
mouseMove
function no longer does anything.
Dropping an Item
Our previous example serves its purpose well, just dragging an item around and leaving it wherever you drop it. Often we have other purposes in mind when we drop the item, however. We could be dragging it to a trash can for instance, or we might want it to snap to a specific area on our page.
Unfortunately we run into a fairly major issue here. Since the item we're
dragging is always directly under our mouse it's impossible to fire a
mouseover
, mousedown
, mouseup
or any other mouse action on any other item on our
page. If you drag an item onto a trashcan your mouse is still on top of that
item, not the trashcan.
How do we get around this? There are several possible solutions. As mentioned
earlier the purpose of our mouseOffset
was so that the item would always be
properly positioned under the mouse. If you ignore this and always make the item
positioned to the bottom-right of the mouse, your cursor will not be hindered by
the item you are dragging and we no longer have a problem. This, however, simply
doesn't look that good. We generally want to keep the item under the mouse for
aesthetic reasons.
Another option is to simply not move the item you are dragging. You can change the cursor to show your user that you are dragging an item, but leave the item in place until you drop it somewhere. This also solves our issue but has the same issue as our previous solution: aesthetics.
Our final solution doesn't affect either the item you are dragging or the item you are dropping onto. Unfortunately it's more difficult to accomplish than either of the previous two solutions to our problem. What we are going to do is get a list of all our drop targets. When the mouse is released we manually check the position of our mouse against the position of each of our targets to see if the mouse was released over one of those targets. If it was then we know we just dropped an item onto our target.
When the mouse is released in this example we start to loop through each of
our possible drop targets. If the mouse cursor is over our target we know that
we have a drop event. To check this we make sure that our cursor is to the right of
the left side of our target (mousePos.x > targetPos.x
) and that the cursor is
to the left of the right side of our target (mousePos.x < (targPos.x +
targWidth)
). We do the same thing for the y-coordinates. If all of these return
true than our cursor is inside of the boundaries of our target.
Pulling It All Together
Finally we have all the pieces we need to create a fully function drag/drop script. The one thing we are going to throw in here is DOM manipulation. If you are not completely familiar with this please read my JavaScript Primer on DOM Manipulation.
The following code takes any "container" or "containers" and makes it possible to drag each item
in those containers. It's the code behind the second demo in this
article. This code can be used to reorder lists, to position "Nav Windows
" on
the left and right of your page, or for any other number of functions you can
think of.
We are going to step through the "pseudo-code" but leave it to the reader to look through the actual code, which is thoroughly commented.
- When the document is first loaded we create a
dragHelper div
. ThisdragHelper
is going to be the "Shadow" item that is dragged around when we start to drag an item. The actual item will not be dragged, just moved usinginsertBefore
andappendChild
. We hide thisdragHelper
to start with. - We have our
mouseDown
andmouseUp
functions. To start with, all these functions do is record the state of the mouse button so that our variable,iMouseDown
, is always true when the mouse button is down and always false when it is not. - We create a global variable,
DragDrops
, and a function,CreateDragContainer
.DragDrops
contains "Sets" of containers that are related to each other. Any arguments (containers) that are passed toCreateDragContainer
are grouped together into a new set so that items can be moved freely between these containers. TheCreateDragContainer
function also ties each item in each container to the set it's in usingsetAttribute
. - Now that our code knows what set each item is in, we move to the
mouseMove
function. ThemouseMove
function first sets a variable, target, to whatever element the mouse is over. If this item is in a set (checked withgetAttribute
) we continue on:- First we run a simple little script that changes the class of our target if necessary. This creates a rollover effect.
- We then check to see if the mouse button was just pressed (since the
last time our code got here). If it was:
- We set our variable,
curTarget
, to the current item. - We record the item's current position in the document so that we can put it back later, if necessary.
- We clone the current item into our
dragHelper
so that we can move around a "Shadow" copy of our item. - Since we have an exact copy of our drag item in our
dragHelper
, the item that will always be under the mouse cursor, we must remove thedragObj
attribute so that our code does not thingdragObj
is in a set. - We take a snapshot of the current position, width and height of every item in our set. We do this here so that we only have to do it once, when the item is first starting to be dragged. Otherwise we would have to do this every time the mouse moved, potentially hundreds of times per second.
- We set our variable,
- If the mouse button was not just pressed we either have the same target we did before or we have no target. In either case we don't do anything at all.
- We now check our
curTarget
variable.curTarget
should only contain an object being dragged, so if it exists we are in the process of dragging an item:- We start by moving our "Shadow" helper div to the mouse cursor. This is just a item that can be dragged as created previously in this article.
- We then check each container in the current set to see if our mouse is
inside of that container.
- If the mouse is in a container we check each item in that container to see where the item we are dragging belongs.
- We then place the item we are dragging either before another item in the container or at the end of the container.
- Finally we make sure our item is visible.
- If the mouse is not in a container we make the item we are dragging around is invisible.
- All that's left is to capture the
mouseUp
event:- First we hide our dragHelper: it's no longer needed since we aren't dragging anything
- If the drag item is visible it's already in it's place in whatever container it belongs in and we're done.
- If the drag item is invisible we put it back where it was before this started.