Draggable elements in javascript have been done hundreds of times before - from tutorials to packages to hacked up half working code files. But, hey, I have decided to throw my two cents in anyway - because we all know you wanted yet another option for dragging in javascript, right? Joking aside, what will will cover today are the detailed inner workings of a homegrown solution to draggable elements. Depending on your level of javascript knowledge, you might want to read two previous tutorials on this site: Javascript Objects - A Useful Example (which covers some basics about how objects work in javascript), and Javascript - Working With Events (which covers how to work with events and event objects). The techniques (and some of the code) from both of those articles will be used in this tutorial.
The capabilities of the solution in this tutorial are pretty small compared to some of the big packages out there now for javascript dragging. But it covers the basics. You can set bounds for dragging, and hand custom functions to be called at the start and end of dragging, as well as after every move. The element that gets moved and the element that has the initial hook can be different (i.e, the draggable element can have a drag handle. And of course, dragging can be detached from an object at any time (and any active dragging canceled).
Below is an extremely simple example of what can be done with what we will cover today. The red box, when dragged, is forced to stay inside the outer box, and when dragging is finished an alert box pops up saying that the drag has ended. The blue box can only be dragged by the red handle at the top of it, but it can be dragged out of the outer box. The button in the upper left turns on and off the ability to drag either box.
So how is all this done? Well, lets start off from the very top - the initialization of a drag object. Here is the function declaration:
startCallback, moveCallback, endCallback, attachLater)
When declaring a drag object, you hand it first the element that is going to be dragged (either the element id (a string) or the element itself). Then it wants the attachElement - which is the element that will be the drag handle. This can also be a string or an element, and if null is passed in, the drag element will be used. Then it takes a lower bound and an upper bound, which are the constraints on where the object can be dragged to. If these are null, there are no bounds. The last three arguments are the callbacks - startCallback gets called at the start of dragging, moveCallback is called after each mouse movement during a drag, and endCallback is called when the drag has completed. Any or all of these can be null. The argument attachLater lets the drag object know if it should start listening for drag events immediately or not. If false or null, it starts listening immediately, but if true, you will have to tell the drag object at a later point to start listening.
You may be wondering what exactly gets passed in for lower and upper bounds - since there need to be 4 values (x and y for both maximum and minimum) and there are only two arguments. Well, to make the dragging code simpler to work with, we us a pretty standard position object:
{
this.X = x;
this.Y = y;
this.Add = function(val)
{
var newPos = new Position(this.X, this.Y);
if(val != null)
{
if(!isNaN(val.X))
newPos.X += val.X;
if(!isNaN(val.Y))
newPos.Y += val.Y
}
return newPos;
}
this.Subtract = function(val)
{
var newPos = new Position(this.X, this.Y);
if(val != null)
{
if(!isNaN(val.X))
newPos.X -= val.X;
if(!isNaN(val.Y))
newPos.Y -= val.Y
}
return newPos;
}
this.Min = function(val)
{
var newPos = new Position(this.X, this.Y)
if(val == null)
return newPos;
if(!isNaN(val.X) && this.X > val.X)
newPos.X = val.X;
if(!isNaN(val.Y) && this.Y > val.Y)
newPos.Y = val.Y;
return newPos;
}
this.Max = function(val)
{
var newPos = new Position(this.X, this.Y)
if(val == null)
return newPos;
if(!isNaN(val.X) && this.X < val.X)
newPos.X = val.X;
if(!isNaN(val.Y) && this.Y < val.Y)
newPos.Y = val.Y;
return newPos;
}
this.Bound = function(lower, upper)
{
var newPos = this.Max(lower);
return newPos.Min(upper);
}
this.Check = function()
{
var newPos = new Position(this.X, this.Y);
if(isNaN(newPos.X))
newPos.X = 0;
if(isNaN(newPos.Y))
newPos.Y = 0;
return newPos;
}
this.Apply = function(element)
{
if(typeof(element) == "string")
element = document.getElementById(element);
if(element == null)
return;
if(!isNaN(this.X))
element.style.left = this.X + 'px';
if(!isNaN(this.Y))
element.style.top = this.Y + 'px';
}
}
This object, while it has a decent amout of code, is pretty simple. It stores two values, an X and a Y coordinate. You can add and substract other position objects using the Add and Subtract functions, which return a new position object with the computed values. There are also the functions Max, Min and Bound. Max will compute the point that is the maximum of the current point object and the point given, and Min will compute the minimum. Bound will constrain the current point within the area given by two arguments - the lower bound and the upper bound. This function uses the Max and Min functions, and returns the constrained point. The function Check just returns a point with the current point's values - but first makes sure that the current point is holding actual numerical values (and if not, sets those values to 0). The Apply function is a useful little one - calling Apply with a element id or an element object wil set the position of the given element to the values held in the current position.
So, back to the original reason this was brought up - the lower and upper bounds passed in when creating a new instance of dragObject are Position objects. As you will see later on in the code, the Position object (and its functions) are used a number of times.
Looking a little bit deeper, we see what happens when a drag object is initialized:
startCallback, moveCallback, endCallback, attachLater)
{
if(typeof(element) == "string")
element = document.getElementById(element);
if(element == null)
return;
if(lowerBound != null && upperBound != null)
{
var temp = lowerBound.Min(upperBound);
upperBound = lowerBound.Max(upperBound);
lowerBound = temp;
}
var cursorStartPos = null;
var elementStartPos = null;
var dragging = false;
var listening = false;
var disposed = false;
if(typeof(attachElement) == "string")
attachElement = document.getElementById(attachElement);
if(attachElement == null)
attachElement = element;
if(!attachLater)
this.StartListening();
}
Nothing that exciting here - we resolve the element and attachElement to actual nodes (if they are id strings). As an error check, we make sure that the upper and lower bounds are actually the upper and lower bounds (and not flipped). We also set up some variables that will be used throughout the drag object. The cursorStartPos gets set when a drag starts with a position object holding the position of the cursor at that time. The elementStartPos also gets set at the start of a drag, and holds a position object filled with the position of the element at the that time. The variables dragging, listening, and disposed are state variables for the drag object - listening means that the drag object is listening for drag start events, dragging means the element is currently being dragged, and disposed means that this drag object has been cleaned up and can no longer be used for dragging. At the end of the code here, we see that if attachLater is not true, we call the function StartListening. That is a public function defined in the drag object, so lets take a look at what it does:
{
if(listening || disposed)
return;
listening = true;
hookEvent(attachElement, "mousedown", dragStart);
}
The first two lines are just checks - if we are already listening for drag events or we are disposed, do nothing and return. Otherwise, set the listening variable to true (because we are about to start listening), and hook the dragStart function into the mousedown event of the attachElement. You may recognize the function hookEvent from the Javascript - Working With Events tutorial, but just as a refresher, here is what hookEvent (and its counterpart, unhookEvent) look like:
{
if(typeof(element) == "string")
element = document.getElementById(element);
if(element == null)
return;
if(element.addEventListener)
element.addEventListener(eventName, callback, false);
else if(element.attachEvent)
element.attachEvent("on" + eventName, callback);
}
function unhookEvent(element, eventName, callback)
{
if(typeof(element) == "string")
element = document.getElementById(element);
if(element == null)
return;
if(element.removeEventListener)
element.removeEventListener(eventName, callback, false);
else if(element.detachEvent)
element.detachEvent("on" + eventName, callback);
}
If you don't understand what is going on in those functions, you should go take a look at that tutorial.
So we hooked the dragStart function - well, what is the dragStart function? It is a private function on drag object:
{
if(dragging || !listening || disposed)
return;
dragging = true;
if(startCallback != null)
startCallback(eventObj, element);
cursorStartPos = absoluteCursorPostion(eventObj);
elementStartPos = new Position(parseInt(element.style.left),
parseInt(element.style.top));
elementStartPos = elementStartPos.Check();
hookEvent(document, "mousemove", dragGo);
hookEvent(document, "mouseup", dragStopHook);
return cancelEvent(eventObj);
}
So this function gets called when the attachElement gets a mousedown event. The first thin we do is make sure that the object is in the right state to do any dragging - if we are already dragging, or are not listening, or are disposed, we do nothing and return. If everything is ok in that department, we set the dragging flag to true and get down to business. If the startCallback is not null, we call it with the mousedown event object and the drag element as arguments. Once that finishes, we determine the absolute position of the cursor and store it (I'll explain that function in a moment). Then we determine the current position of the drag element by parsing the appropriate style tags. If those style tags are parsed incorrectly for whatever reason, we assume the value is 0. Then there are two more hookEvent calls - one for movement, where we hook the dragGo function to the mousemove event of the document, and the other for ending the drag, where we hook the dragStopHook function to the mouseup event of the document. The reason we hook these events to the document, and not to, say, the drag element itself, is that it is very easy for the browser to get a little bit behind and for the cursor to slip out of the area contained by the drag element. When that happens, the mousemove and mouseup events do not get fired for the drag element - and if we were listening to those events, the drag would stop working. So instead, we hook to the events for the document - which means that dragging will work as long as the cursor does not leave the browser window. Finally, we cancel the event and return (we don't want anyone else who might listen to the mousedown event interfering with the drag - for instance, text selection). The cancelEvent function is again from the Javascript - Working With Events tutorial, but heres the code for it anyway:
{
e = e ? e : window.event;
if(e.stopPropagation)
e.stopPropagation();
if(e.preventDefault)
e.preventDefault();
e.cancelBubble = true;
e.cancel = true;
e.returnValue = false;
return false;
}
There were a couple functions in that dragStart function that we haven't defined yet - so lets go through them one at a time. First, we have absoluteCursorPostion. This function takes an event object and returns a position object containing the position of the cursor:
{
eventObj = eventObj ? eventObj : window.event;
if(isNaN(window.scrollX))
return new Position(eventObj.clientX + document.documentElement.scrollLeft
+ document.body.scrollLeft,
eventObj.clientY + document.documentElement.scrollTop
+ document.body.scrollTop);
else
return new Position(eventObj.clientX + window.scrollX,
eventObj.clientY + window.scrollY);
}
The reason we need this function is because of scrolling - Internet Explorer has a non-standard way of getting how much the window is scrolled. The top block of the if statement is for Internet Explorer, and the bottom block is for most other browsers.
Next we get to the real meat of dragging - a private function part of the drag object called dragGo. Here we have the code for that function:
{
if(!dragging || disposed)
return;
var newPos = absoluteCursorPostion(eventObj);
newPos = newPos.Add(elementStartPos);
newPos = newPos.Subtract(cursorStartPos);
newPos = newPos.Bound(lowerBound, upperBound);
newPos.Apply(element);
if(moveCallback != null)
moveCallback(newPos, element);
return cancelEvent(eventObj);
}
Surprisingly, this function is not that complicated. Like the others, first we check to make sure that we are in the correct state - if we aren't supposed to be dragging, or we are disposed, we return without doing anything. Then we calculate the current position of the cursor, again using the absoluteCursorPostion function. Using the new cursor position, we calculate the new element position using the original element position and the original cursor position. There are a number of dragging scripts out there that don't do absolute calculations on every move - they do moves relative from the last cursor and last element position. But I've found that its generally better to always calculate the new position for the very original position - it reduces the risk of errors building up over time as the user continues to drag the object without ever dropping it. Mostly, its just a personal preference.
After the new position is calculated, we call Bound on it to make sure it is within the bounds given to the drag object. Once that is all done, we use the Apply function on the new postion to set the position of the element. Then, if the moveCallback isn't null, we call it, and give it the new position and the drag element as arguments. Finally, as always, we cancel the event.
One more function was referenced back in the dragStart code - dragStopHook. This function is a private function of the drag object, and is hooked into the mouseup event to end the dragging:
{
dragStop();
return cancelEvent(eventObj);
}
This is really just a wrapper function around dragStop, but it also, as always, cancels the event. So lets take a look at dragStop:
{
if(!dragging || disposed)
return;
unhookEvent(document, "mousemove", dragGo);
unhookEvent(document, "mouseup", dragStopHook);
cursorStartPos = null;
elementStartPos = null;
if(endCallback != null)
endCallback(element);
dragging = false;
}
Here, if we aren't in the correct state (i.e., if we aren't dragging or we are disposed) we return without doing anything. Otherwise, we continue and unhook the dragGo and dragStopHook from the mousemove and mouseup events. We clear out the cursorStartPos and elementStartPos, because they are no longer valid. If the endCallback is not null, we call it with the drag element as the argument. Finally, we set the dragging flag to false.
There are a couple other simple functions that the drag object has, in order to add a bit more functionality. First off, we have these three very simple public property functions:
this.IsListening = function() { return listening; }
this.IsDisposed = function() { return disposed; }
These functions allow outside javascript code to determine the current state of a drag object.
We also have the public StopListening function - which is the counterpart to the StartListening function:
{
if(!listening || disposed)
return;
unhookEvent(attachElement, "mousedown", dragStart);
listening = false;
if(stopCurrentDragging && dragging)
dragStop();
}
This function tells the drag object to stop listening for drag events. If the object is disposed, or already not listening, it does nothing and returns. Otherwise, it unhooks the dragStart function from the mouseDown event on attachElement. Then we set the listening flag to false. There is always a possibility that the element is actually currently being dragged when this function is called - so it takes an argument to determine if the current drag operation should be stopped. If the argument (stopCurrentDragging) is true, and the object is current in the dragging state, we call the dragStop function.
Finally, we have the dispose function (which is also public):
{
if(disposed)
return;
this.StopListening(true);
element = null;
attachElement = null
lowerBound = null;
upperBound = null;
startCallback = null;
moveCallback = null
endCallback = null;
disposed = true;
}
This is just a cleanup function - for when you don't need the drag object anymore. It makes sure all events are unhooked, and clears out all its element references and variables. Not a necessary function, but if creating and discarding a lot of drag objects, its a good thing to have (and to remember to call).
And if you take all of that code and put it together, this is what the drag object (and its helper functions) looks like:
{
if(typeof(element) == "string")
element = document.getElementById(element);
if(element == null)
return;
if(element.addEventListener)
element.addEventListener(eventName, callback, false);
else if(element.attachEvent)
element.attachEvent("on" + eventName, callback);
}
function unhookEvent(element, eventName, callback)
{
if(typeof(element) == "string")
element = document.getElementById(element);
if(element == null)
return;
if(element.removeEventListener)
element.removeEventListener(eventName, callback, false);
else if(element.detachEvent)
element.detachEvent("on" + eventName, callback);
}
function cancelEvent(e)
{
e = e ? e : window.event;
if(e.stopPropagation)
e.stopPropagation();
if(e.preventDefault)
e.preventDefault();
e.cancelBubble = true;
e.cancel = true;
e.returnValue = false;
return false;
}
function Position(x, y)
{
this.X = x;
this.Y = y;
this.Add = function(val)
{
var newPos = new Position(this.X, this.Y);
if(val != null)
{
if(!isNaN(val.X))
newPos.X += val.X;
if(!isNaN(val.Y))
newPos.Y += val.Y
}
return newPos;
}
this.Subtract = function(val)
{
var newPos = new Position(this.X, this.Y);
if(val != null)
{
if(!isNaN(val.X))
newPos.X -= val.X;
if(!isNaN(val.Y))
newPos.Y -= val.Y
}
return newPos;
}
this.Min = function(val)
{
var newPos = new Position(this.X, this.Y)
if(val == null)
return newPos;
if(!isNaN(val.X) && this.X > val.X)
newPos.X = val.X;
if(!isNaN(val.Y) && this.Y > val.Y)
newPos.Y = val.Y;
return newPos;
}
this.Max = function(val)
{
var newPos = new Position(this.X, this.Y)
if(val == null)
return newPos;
if(!isNaN(val.X) && this.X < val.X)
newPos.X = val.X;
if(!isNaN(val.Y) && this.Y < val.Y)
newPos.Y = val.Y;
return newPos;
}
this.Bound = function(lower, upper)
{
var newPos = this.Max(lower);
return newPos.Min(upper);
}
this.Check = function()
{
var newPos = new Position(this.X, this.Y);
if(isNaN(newPos.X))
newPos.X = 0;
if(isNaN(newPos.Y))
newPos.Y = 0;
return newPos;
}
this.Apply = function(element)
{
if(typeof(element) == "string")
element = document.getElementById(element);
if(element == null)
return;
if(!isNaN(this.X))
element.style.left = this.X + 'px';
if(!isNaN(this.Y))
element.style.top = this.Y + 'px';
}
}
function absoluteCursorPostion(eventObj)
{
eventObj = eventObj ? eventObj : window.event;
if(isNaN(window.scrollX))
return new Position(eventObj.clientX + document.documentElement.scrollLeft
+ document.body.scrollLeft,
eventObj.clientY + document.documentElement.scrollTop
+ document.body.scrollTop);
else
return new Position(eventObj.clientX + window.scrollX,
eventObj.clientY + window.scrollY);
}
function dragObject(element, attachElement, lowerBound, upperBound,
startCallback, moveCallback, endCallback, attachLater)
{
if(typeof(element) == "string")
element = document.getElementById(element);
if(element == null)
return;
if(lowerBound != null && upperBound != null)
{
var temp = lowerBound.Min(upperBound);
upperBound = lowerBound.Max(upperBound);
lowerBound = temp;
}
var cursorStartPos = null;
var elementStartPos = null;
var dragging = false;
var listening = false;
var disposed = false;
function dragStart(eventObj)
{
if(dragging || !listening || disposed)
return;
dragging = true;
if(startCallback != null)
startCallback(eventObj, element);
cursorStartPos = absoluteCursorPostion(eventObj);
elementStartPos = new Position(parseInt(element.style.left),
parseInt(element.style.top));
elementStartPos = elementStartPos.Check();
hookEvent(document, "mousemove", dragGo);
hookEvent(document, "mouseup", dragStopHook);
return cancelEvent(eventObj);
}
function dragGo(eventObj)
{
if(!dragging || disposed)
return;
var newPos = absoluteCursorPostion(eventObj);
newPos = newPos.Add(elementStartPos).Subtract(cursorStartPos);
newPos = newPos.Bound(lowerBound, upperBound)
newPos.Apply(element);
if(moveCallback != null)
moveCallback(newPos, element);
return cancelEvent(eventObj);
}
function dragStopHook(eventObj)
{
dragStop();
return cancelEvent(eventObj);
}
function dragStop()
{
if(!dragging || disposed)
return;
unhookEvent(document, "mousemove", dragGo);
unhookEvent(document, "mouseup", dragStopHook);
cursorStartPos = null;
elementStartPos = null;
if(endCallback != null)
endCallback(element);
dragging = false;
}
this.Dispose = function()
{
if(disposed)
return;
this.StopListening(true);
element = null;
attachElement = null
lowerBound = null;
upperBound = null;
startCallback = null;
moveCallback = null
endCallback = null;
disposed = true;
}
this.StartListening = function()
{
if(listening || disposed)
return;
listening = true;
hookEvent(attachElement, "mousedown", dragStart);
}
this.StopListening = function(stopCurrentDragging)
{
if(!listening || disposed)
return;
unhookEvent(attachElement, "mousedown", dragStart);
listening = false;
if(stopCurrentDragging && dragging)
dragStop();
}
this.IsDragging = function(){ return dragging; }
this.IsListening = function() { return listening; }
this.IsDisposed = function() { return disposed; }
if(typeof(attachElement) == "string")
attachElement = document.getElementById(attachElement);
if(attachElement == null)
attachElement = element;
if(!attachLater)
this.StartListening();
}
Now that we have this wonderful drag object, how do we use it? Below is the javascript and html code for creating the example above:
<input type="button" value="Stop Listening" onclick="buttonClicked();"
id="dragButton" style="position:absolute;right:0px;"/>
<div id="exampleA" style="position:absolute;top:10px;
left:10px;width:50px;height:50px;background-color:Red;"></div>
<div id="exampleB" style="position:absolute;top:10px;
left:70px;width:50px;height:50px;background-color:Blue;">
<div id="exampleBHandle" style="position:absolute;
height:20px;width:50px;left:0px;top:0px;cursor:move;background-color:Red;">
Handle
</div>
</div>
</div>
{
var button = document.getElementById('dragButton');
if(exampA.IsListening())
{
exampA.StopListening(true);
exampB.StopListening(true);
button.value = "Resume Listening";
}
else
{
exampA.StartListening();
exampB.StartListening();
button.value = "Stop Listening";
}
}
function exampleAEnd()
{
alert("Drag Ended.");
}
var exampA = new dragObject("exampleA", null, new Position(0,0),
new Position(591,200), null, null, exampleAEnd, false);
var exampB = new dragObject("exampleB", "exampleBHandle");
The bottom two lines (where the drag objects are created) are the most important. For the first one, we want to drag the element "exampleA". The attach element will be the same as the drag element, so we pass null for that argument. Then we pass the lower and upper bounds. We have no start and move callbacks, so we pass null for both of those, but we pass exampleAEnd for the end callback (which is simple little function defined right above). And finally, we want the drag object to start listening immediately, so we pass false for the last argument (attachLater).
The code for creating the drag object for the element "exampleB" is even simpler. Here, we take advantage of the fact that since we don't intend to pass anything for the last 6 arguments, we can leave them off the call altogether (the arguments will get the value of undefined in the drag object, which is equal to null). So here the only things we pass are the string "exampleB" (the element to drag) and the string "exampleBHandle" (the id of the handle div on top of the exampleB div).
So now those two elements are listening for drag events. The example gets a bit more meat to it with the buttonClicked method - which turns on and off listening for drag events on the two objects. If we are listening (by checking the IsListening function), we call StopListening on both drag objects (and we change the text of the button). Otherwise, we call StartListening (and again change the text on the button). And thats all the code you need!
Hopefully, you found this detailed look in to dragging in javascript interesting and informative. If you would like a copy of the javascript code, here is a link. Feel free to play with it, use it, pretty much do whatever you want. And, as always, comments and questions are welcome - just leave them in the comments.
10/26/2007 - 00:35
I am very impressed with the methodology. I've done this with success on ie browsers. This is very cross-browser-compatible.
01/21/2008 - 05:37
Thank you to all those contributing to this site. I found myself a good tutor
03/07/2008 - 06:30
Hey, I haven't yet read more than the beginning, but this seems to be exactly what I was looking for. I'll come back with more comments when I have tried it.
04/08/2008 - 15:20
I just tried your code and it failed. I copied it exactly as you have it. I clicked on the button and got the error:
Error: exampA.IsListening is not a function
Source File: file:///Users/rich/dhtml/Test.html
Line: 291
I copied the first batch of code and paste it in the html doc and then the last batch just below the first batch in the same doc then the div code. I am not able to move the boxes. Any idea why?
04/08/2008 - 18:54
I'm pretty sure the problem is that when the code goes to create the dragObject for the element "exampleA", the element does not yet exist. You need to move the second chuck of javascript code down below the html code for the example divs. Let me know if that fixes it for you.
04/18/2008 - 06:56
Very good. Thanks a lot.
05/04/2008 - 04:14
thanxs for the great info
07/20/2008 - 21:59
Um…I don’t know why you made the drag and drop so complex. It takes about 4-5 functions and a bit more then 30 lines of code to get a generic drag and drop working that is cross browser compatible. This is the first time I’m visiting your site, and its not a coding style that I’m used too. There are many ways to get something done, the more lines of code does not necessarily mean the smartest way to do it.
07/20/2008 - 22:01
Just so you know, I am in no way implying your code is wrong. Your code is probably better and more efficient for all I know. I'm not a programmer anyways, I just program whenever I need too, but I am a newbie in comparison to you guys. Keep up the good work!
Put each section in different files
-----------------------------------------
//HTML:
-----------------------------------------
<head>
<link rel="stylesheet" type="text/css" href="dragNdrop.css">
<script language="javascript" type="text/javascript" src="dragNdrop.js"></script>
</head>
<body>
<div class="drag" id="drag1"><p>anything goes here</p></div>
<div class="drag" id="drag2"><p>anything goes here</p></div>
</body>
</html>
------------------------------------------
//CSS:
------------------------------------------
position: absolute;
width: 100px;
border: 2px solid black;
border-top: 20px solid blue;
top: 50px;
padding: 5px;
}
#drag1 { left: 15px; }
#drag2 { left: 200px; }
------------------------------------------
//Javascript explained:
a) "onmousedown" even triggers for dragging the object if user clicks the object
b) "onmousemove" for updating the objects position
c) if b) starts, "onmouseup" event drops the object
a few things we need:
---------------------
*need to get the mouse position (included in Drag() function)
*need to get rid of jump effect by calculating the offset between
the top left corner of the object and the mouse position(included in Move() function)
-------------------------------------------
//Javascript Code:
-------------------------------------------
//Mine would be a waste of time explaining in comparison to yours
function Setup() {
if(!document.getElementsByTagName) return;
objects = document.getElementsByTagName("DIV");
for(i=0;i<objects.length; i++)
{
if(objects[i].className !="drag") continue;
objects[i].onmousedown = Drag;
}
}
function Drag(e) {
if(!e) var e= window.event;
obj = (e.target) ? e.target: e.srcElement;
obj.style.borderColor = "green";
dx = x - obj.offsetLeft;
dy = y - obj.offsetTop;
}
function Move(e) {
if(!e) var e= window.event;
if(e.pageX)
{
x=e.pageX;
y=e.pageY;
}
else if(e.clientX)
{
x=clientX;
y=clientY;
}
else return;
if(obj)
{
obj.style.left = x - dx;
obj.style.top = y - dy;
}
}
function Drop() {
if(!obj) return;
obj.style.borderColor="blue";
obj = false;
}
document.onmousemove = Move;
document.onmouseup = Drop;
window.onload = Setup;
--------------------------------------------------------
07/21/2008 - 10:24
Feel free to email us the code and I will post it here in your comment. I am interested in seeing your solution.
07/21/2008 - 13:34
FYI... I have edited TheDesigner's comment with an alternate solution to drag and drop.
11/12/2008 - 03:39
a little mistake in Designer's code
{
x=clientX;
y=clientY;
}
Must be
{
x=e.clientX;
y=e.clientY;
}
12/04/2009 - 08:58
Hi Yuva,
I need to movable those divs in the particular region only, for which, where i change the javascript?? I have main div that contains these 2 divs and movable only in that region, pls help
07/21/2008 - 06:28
Long? I'll admit that it is long. Complex? I'm not so sure - my first piece of drag code for javascript was around 40 lines, and it wasn't exactly easy to understand, or easy to reuse.
The point of this code is exactly the opposite of getting it done in a 'quick and dirty' way. This code is supposed to be reusable for any application of dragging that you can come up with - and so things like start and stop methods, and bounds checking add a decent amount of code.
Also, what are you counting as "Drag And Drop" code here? Is it all of the code on this page, or is it just the drag object itself? Because the drag object is only 120 lines - not a terrible amount. And guess what? The core three functions (dragStart, dragGo, and dragStop) total 45 lines.
It is when you count the rest of the code that it becomes really long. But this is all about encapsulation and reuse. Those event functions and that position object? They are handy in a lot more places than just drag and drop code - in fact, they have been used in a number of other tutorials on this blog.
We could probably argue about this all day, but in the end it comes down to coding style preferences and how someone measures the quality of code. Really, it just looks to me like we are weighing factors differently in determining that quality - and I'm not sure there is a 'right' answer for that.
Edit:
I like your idea of marking the elements for dragging with a CSS class - I might co-opt that idea if I ever feel like re-writing my drag class again. The only issue I would have is how you are attaching events - doing event attaching that way will override anyone else who has attached to those events, so it could potentially break other code. On the other hand, it is a lot cleaner that way, and if you know for sure that you are the only one touching those events then it is fine.
09/25/2008 - 04:05
Amazing tutorial!!
@The Designer: yours too!
10/20/2008 - 04:01
Hi , I found your code very useful.
But I ahve a small problem initially i.e. for the first time when i try to move the handle that time the object just seems to change its position abruptly it then works fine. Can you suggest a solution please ?
Thanks
04/11/2009 - 09:06
I ran into the same thing and traced it to a requirement to use inline styles for the div you are moving, rather than placing them in the CSS file. The styles for the container and the handle can be in the CSS file.
If anyone sees a way around this please post a response.
10/28/2009 - 20:26
I had the same problem and followed SteveL's hints. However I think SteveL got it backwards. I had to use inline styles for the handles only.
The textbox I was using did not start at the minimal size. If inline style was not used for the handle the box resized to the minimal size in the dimension(s) controlled by the handle the first time the handle was used.
I got the same effect in the latest FF, Chrome, and IE7. Looks like a standard!
10/24/2008 - 15:17
Very useful to me, I easily can drop all functions that don't need. Thanks!
11/26/2008 - 08:08
Respect for tutorial ,
Nicely explained and very reusable code , helped me a lot. So thanks a lot :).
01/07/2009 - 01:57
Awesome tutorial. Finally some information that doesn’t involve using Scriptaculous. I implemented this code a while ago and now I’m revisiting it on another project! Its very easy to expand functionality.
03/20/2009 - 05:27
- I have div A that contain 3 childs - div B and C and D, how can i made that if i hold the mouse on D and move, div A will not move and otherwise, hold on div B and C and move mouse will move div A ?
06/16/2009 - 09:57
Many thanks for the time and effort... extremely helpful and very clearly explained and having a universal model is a definite thumbs-up, but I've not fully understood the final implementation bit. I have an 'in-yer-face' image element that's used to resize table columns when a header cell is clicked, but once it's declared as a DragObject can't see how to initialize it to locate over a column border before dragging starts. 'element.style.left = iLeft + "px"' appears to have no effect.
11/18/2010 - 14:08
Code is not working when position is set by "%", for example in the case of this table:
11/18/2010 - 13:13
I modified the code to avoid this issue, these are the modifications made:
Added "ApplyMargin" function to "Position" object:
{
if(typeof(element) == "string")
element = document.getElementById(element);
if(element == null)
return;
if(!isNaN(this.X))
element.style.marginLeft = this.X + 'px';
if(!isNaN(this.Y))
element.style.marginTop = this.Y + 'px';
}
Aded "useMargins" variable and modify "dragStart" and "dragGo" functions in "dragObject" object:
function dragStart(eventObj)
{
if(dragging || !listening || disposed) return;
dragging = true;
if(startCallback != null)
startCallback(eventObj, element);
cursorStartPos = absoluteCursorPostion(eventObj);
if (element.style.left.indexOf("%") > 0 || element.style.top.indexOf("%") > 0 ) //Begin Added Code
{
useMargins = true;
elementStartPos = new Position(parseInt(element.style.marginLeft), parseInt(element.style.marginTop));
}
else //End Added Code
elementStartPos = new Position(parseInt(element.style.left), parseInt(element.style.top));
elementStartPos = elementStartPos.Check();
hookEvent(document, "mousemove", dragGo);
hookEvent(document, "mouseup", dragStopHook);
return cancelEvent(eventObj);
}
function dragGo(eventObj)
{
if(!dragging || disposed) return;
var newPos = absoluteCursorPostion(eventObj);
newPos = newPos.Add(elementStartPos).Subtract(cursorStartPos);
newPos = newPos.Bound(lowerBound, upperBound)
if (useMargins) //Begin Added Code
newPos.ApplyMargin(element);
else //End Added Code
newPos.Apply(element);
if(moveCallback != null)
moveCallback(newPos, element);
return cancelEvent(eventObj);
}
;-)
12/20/2010 - 15:11
Thanks for the tutorial. I expanded upon this foundation to create an algebra tile program for use in algebra or pre-algebra class.
http://www.sethammons.com/_reddawndown/playground/algebratiles/
12/23/2010 - 07:27
I'm modifying this code for dynamic menu system that can be edited and rearranged by an authorised user. I have made modifications so that if you drag and hover 1 item over another so the sencond item turns yellow, when you drop it adds a margin to left to indicate it's a subcategory of the the above item. Otherwise it will just rearrange the list like normal. You can see a working example of this at:
http://sandbox.vieswebdesign.com/myMenu.html
My problem is that I want to be able to drag the subscategories with parent category drop them all together in the new location.
have modified load function like so (online version: http://sandbox.vieswebdesign.com/myMenu_1.html):
{
List = document.getElementById("list");
PlaceHolder = document.createElement("div");
PlaceHolder.className = "list";
PlaceHolder.style.backgroundColor = "rgb(225,225,225)";
PlaceHolder.SourceI = null;
for (var i = 0; i < List.childNodes.length; i++) {
if (List.childNodes[i].className == "list") {
var itemId = List.childNodes[i].id.replace("dv", "");
new dragObject("dv" + itemId, "hndl" + itemId, null, null, itemDragBegin, itemMoved, itemDragEnd, false);
var baseMrgn = 0;
if (List.childNodes[i].style.marginLeft != null) {
baseMrgn = List.childNodes[i].style.marginLeft.replace("px", "");
if (!isNaN(parseInt(baseMrgn))) {
baseMrgn = parseInt(List.childNodes[i].style.marginLeft.replace("px", ""));
} else {
baseMrgn = 0;
}
}
for (var j = i; j < List.childNodes.length; j++) {
if (List.childNodes[j].className == "list") {
var subMrgn = 0;
if (List.childNodes[j].style.marginLeft != null) {
subMrgn = List.childNodes[j].style.marginLeft.replace("px", "");
if (!isNaN(parseInt(subMrgn))) {
subMrgn = parseInt(List.childNodes[j].style.marginLeft.replace("px", ""));
} else {
subMrgn = 0;
}
}
if (j > i && subMrgn <= baseMrgn) {
break;
} else {
if (j != i) {
var baseId = List.childNodes[i].id.replace("dv", "");
var itemId = List.childNodes[j].id.replace("dv", "");
new dragObject("dv" + itemId, "hndl" + baseId, null, null, itemDragBegin, itemMoved, itemDragEnd, false);
}
}
}
}
}
}
}
have also added a call to the load() function in itemDragEnd() function.
This allows me to drag objects together but has a few bugs. Whne I drop the items only parent node has moved and the children return to their orignal positions. Also I need to be able to clear the dragObject() associations and recreate them coz otherwise even though the items appear seperate on the menu, the children still move if the parent is moved and if just the children move the can't be added as children to another node.
Kinda complicated to explain but hope it makes sense.
Let me know if you have any ideas or a better way of moving multiple items at once.
Thanks
12/23/2010 - 07:30
Oops sorry that was meant to posted on the Drop & Drop List tutorial page.
My Bad
01/30/2011 - 03:12
Hy, I wanna know when I select div 1 to drag, div 2 to changes automatically positions too, like an google map.
What is the concept? What I need to implement or change on dragging functions?
Thanks!
01/30/2011 - 03:15
Sorry for my 2nd post, but I can't edit first, so I add more. It is possible to make a drag on multiple elements? And how?
01/30/2011 - 05:44
I don't see any way to get the mouse position at the end of the drag. How do I get that information?
02/04/2011 - 05:15
I need one modification - my list is bigger then window size (height is bigger) so I need some modification in mouse position I dont know how to do it ...
02/04/2011 - 05:22
never mind it looks, that I have somewhere error - thanks for this !!!
02/28/2011 - 16:17
very useful code ! Thanks a lot..
03/29/2011 - 10:23
function getStyle(el, property) {
if(typeof el == 'string')
el = document.getElementById(el);
if(!el || !property) return;
var value = el.style[property];
if(!value) {
if(document.defaultView && document.defaultView.getComputedStyle) {
var css = document.defaultView.getComputedStyle(el, null);
value = css ? css.getPropertyValue(property) : null;
} else if (el.currentStyle) {
value = el.currentStyle[property];
}
}
return value == 'auto' ? '' : value;
}
as I start dragging a element, for example div#exampleBHandle, the element will first set its left and top as 0 if the style is not inline but defined in external css file. so for getting the correct left and top. I think getStyle() is necessary...thanks for your tutor, buddy!
07/19/2011 - 05:02
Hi there! very good tutorial, and it works great!!!
i have a doubt this is the code to drag my div:
var example = new dragObject(mainDiv, anchorDiv, new Position(0,0), new Position(maxWidth-maxDivWidth, maxHeight-maxDivHeight), exampleAStart(mainDiv), null, exampleAEnd, false);
this is to have the div draggable into a client screen dynamically, problem is if i have the div into another div the new Position(0,0) is refered to the parent div but i would like to be the position about the body and not the parent div so the draggable div will be alway bounded to the client screen
How can i fix it?? Many many thx
08/07/2011 - 12:24
To fix jumping drag elements when left and top styles are applied:
(in function dragStart)
elementStartPos = new Position(element.offsetLeft, element.offsetTop);
It is a mistake to refer to .style in this application anyhow, and after that change 'style' doesn't appear in the drag objects anymore, although it does still appear in the Position objects. That should probably be fixed as well.
10/24/2011 - 19:52
Here is the simplest Drag implementation i have created and successfully use it for my custom error message box
Works with IE, Google, Firefox, Safari with NOP.
hope will work for you also
<style>
.btn_submit{
background-color:#C8C8C8;font-family:Courier New;font-size:12px;color:#003366;
text-align:Center;font-style:Normal;width:80px;}
</style>
</header>
<body onmousemove="doDrag(event)" >
<table id='msgboxId' border='0'
style='width: 300px; height: 150px; top: 150px; left: 500px; position: absolute; display: noneT; z-index: 1000; opacity: 1000;'>
<tr>
<td>
<table style='width: ; height: 150px; border: #B8B6B8 solid 2px; background-color: #ffffff;'>
<tr class='rowheader'>
<td class='btn_submit' style='width: 295px; height: 15px; background-color:'
onmousedown="setClient(event,'msgboxId')" onmouseup="resetClient(event,'msgboxId')">
</td>
<td id='closeme' style='width: 19px; font-size: 12px; vertical-align: text-top; text-align: center' title='close'>
<a id='icon' href="javascript:doClose('msgboxId')" title='close' style='text-decoration:none; border:0px none;'>
X
</a>
</td>
</tr>
<tr>
<td colspan='2' style='height: 95%'>
<table align='center' border='0' height='95%' width='95%'>
<tr>
<td id='errMsg' colspan='1' class='error_msg'
style='text-align: center; height: 95%; width: 90%'>Message Body</td>
</tr>
<tr>
<td style='height: 10px' colspan='1'> </td>
</tr>
<tr>
<td style='height: 10px; width: 100%;' 1' align='left' nowrap='nowrap' >
<div class='pad' style='height: 10px; width: 100%;'>
<div id='progressid' style='width: 0%; display: none;' align="left">
<hr style='height: 8px; width:100%; background-color: #0033cc' />
</div>
</div>
</td>
</tr>
<tr id='msgbuttons'>
<td colspan='1'>
<table align='center' border='0' height='95%' width='95%'>
<tr>
<td style='text-align: right; width: 20%'>
<div class='btn_submit' id='btnYes' style='width:80px'><a href="javascript:doClose('msgboxId')" class='btn_exclam'>Yes</a></div>
</td>
<td style='width: 60%' align='center'>
<div class='btn_submit' id='btnOK' style='width:80px'><a href="javascript:doClose('msgboxId')" class='btn_submit'>OK</a></div>
</td>
<td style='text-align: right; width: 20%'>
<div class='btn_submit' id='btnNo' style='width:80px'><a href="javascript:doClose('msgboxId')" class='btn_submit'>Cancel</a></div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td></tr></table>
<script type="text/javascript">
var xdiv;
var clx;
function setClient(e, id) {
xdiv = document.getElementById(id);
clx = e.clientX - parseInt(xdiv.style.left);
xdiv.style.cursor = "move";
document.getElementById("errMsg").innerText="Drag Started"
}
function resetClient(e, id) {
xdiv = document.getElementById(id);
xdiv.style.cursor = "default";
xdiv = null;
document.getElementById("errMsg").innerText = "Drag Complete"
}
function doDrag(e) {
if (xdiv == null) {
return false;
}
xdiv.style.top = ((e.clientY)- 15) + "px";
xdiv.style.left = (e.clientX - clx) + "px";
}
//side finctaions
function doClose(msgboxId) {
document.getElementById(msgboxId).style.display = 'none';
}
function doShow() {
document.getElementById('msgboxId').style.display = ''
document.getElementById("errMsg").innerText = "Error Message"
}
</script>
<input type="button" value="Restore" onclick="doShow()" />
</body>
11/29/2011 - 03:41
Thank you so much for your detailed and informative tutorial! And I'm glad to see someone else using the style of coding I do (ie, not putting the open curly brace on the same line but instead on the next line - I swear everhyone does that but to meit seems much harder to read... plus at college we were actually penalized for sloppy code if we did that). I'll be the first to admit that I'm anal when it comes to coding, but it seems like most everyone's code you encounter is inconsistent and/or hard to read... it makes me happy to see someone else interested in the elegance of artistically-constructed code.
Add Comment
[language] [/language]
Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]
See here for supported languages.
Javascript must be enabled to submit anonymous comments - or you can login.