How to Drag and Drop in JavaScript / Page 2 | WebReference

How to Drag and Drop in JavaScript / Page 2


[previous] [next]

How to Drag and Drop in JavaScript [con't]

Demo - Drag any of the images

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.

To accomplish this task 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.

Demo - Drag any image onto the trashcan

     

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.

  1. When the document is first loaded we create a dragHelper div. This dragHelper 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 using insertBefore and appendChild. We hide this dragHelper to start with.
  2. We have our mouseDown and mouseUp 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.
  3. 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 to CreateDragContainer are grouped together into a new set so that items can be moved freely between these containers. The CreateDragContainer function also ties each item in each container to the set it's in using setAttribute.
  4. Now that our code knows what set each item is in, we move to the mouseMove function. The mouseMove function first sets a variable, target, to whatever element the mouse is over. If this item is in a set (checked with getAttribute) we continue on:
    1. First we run a simple little script that changes the class of our target if necessary. This creates a rollover effect.
    2. We then check to see if the mouse button was just pressed (since the last time our code got here). If it was:
      1. We set our variable, curTarget, to the current item.
      2. We record the item's current position in the document so that we can put it back later, if necessary.
      3. We clone the current item into our dragHelper so that we can move around a "Shadow" copy of our item.
      4. 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 the dragObj attribute so that our code does not thing dragObj is in a set.
      5. 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.
    3. 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.
  5. 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:
    1. 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.
    2. We then check each container in the current set to see if our mouse is inside of that container.
      1. If the mouse is in a container we check each item in that container to see where the item we are dragging belongs.
      2. We then place the item we are dragging either before another item in the container or at the end of the container.
      3. Finally we make sure our item is visible.
    3. If the mouse is not in a container we make the item we are dragging around is invisible.
  6. All that's left is to capture the mouseUp event:
    1. First we hide our dragHelper: it's no longer needed since we aren't dragging anything
    2. If the drag item is visible it's already in it's place in whatever container it belongs in and we're done.
    3. If the drag item is invisible we put it back where it was before this started.

[previous] [next]