Javascript example - drag and drop

parameter value
navigator.appName = 
window.event = 
auto event = 
algorithm = 
dragobj = 
mousex,y = ,  
grabx,y = ,  
orix,y = ,  
elex,y = ,  

movable obj1
This is a movable object. Click it and drag it somewhere else. The values in the table will change to show you what's happening.
movable obj2
This is a movable object. Click it and drag it somewhere else. The values in the table will change to show you what's happening.
movable obj3
This is a movable object. Click it and drag it somewhere else. The values in the table will change to show you what's happening.

explanation of parameters


This page demonstrates how to animate the movement of DOM elements in a click-and-drag fashion according to user intervention. The three colored boxes above are each initialized with mousedown events which call a grab function. Try to click-and-drag each of them and watch the table to monitor the javascript variables as you interact with them.


navigator.appName

This is how your browser identifies itself. On my WindowsXP machine Mozilla, and Firefox both identify themselves as "Netscape". IE and Opera7 identify themselves as "Microsoft Internet Explorer". In Opera7 you can actually hit 'F12' and change this so that it identifies itself as "Netscape", but it has no effect on how the rest of the parameters operate. Generally speaking, it appears that Opera supports methods from both IE and Netscape.

Many javascript programmers use this attribute to test which methods should be used for various tasks in the script. We basically ignore it in our script, but I wanted to display it for informational purposes.


window.event

There are different ways to test the system for mouse events. In IE we can call window.event. from within any function at any time in order to get information about the mouse events. If your browser supports window.event, then all of the events will be listed here as they are detected.


auto event

If your browser supports automatic event parameter passing, then you will see the mousemove event in this box when that event is detected. Other events do not show up here because we did not set up those functions to pass the current event. Our code dynamically changes the mousemove event with document.onmousemove=update and document.onmousemove=drag. Those functions are declared as update(e) and drag(e). Netscape browsers automatically pass the event. IE browsers do not. document.onmousemove=update(event) causes an error and cannot be done. Consequently, we cannot force IE to pass a parameter here.

Note: Opera7 apparently supports both automatic event passing and window.event. It can also be set to identify itself as either "Microsoft Internet Explorer" or "Netscape" as well as "Opera".


algorithm

Support for two different properties is tested for. Those properties are e.clientX and e.pageX where e is the mousemove event. These are two different kinds of coordinates that you can get from the event object. Other alternative mouse coordinate pairs include layerX,layerY; offsetX,offsetY; screenX,screenY; and x,y; each representing coordinates relative to different objects.

It would be my preference to use the pageX,pageY pair, because they represent the coordinates on my page regardless of scrollbars, but this pair isn't supported on all browsers. My second choice is to use clientX,clientY and account for scrollbars.

This field shows which of the two algorithms are supported (or neither or both). The first one in the list is the one actually being used by the script in this page. It doesn't really matter which pair we decide to use in this script, because we are measuring distances relative to our click point on the same coordinate system. Different browsers though, will send different numbers for the clientX,Y pair, even though you may think that you are hovering over the same identical point on the page. So beware how you use these values.


dragobj

Once the grab function has been called, the script stores the name of the object whose mousedown event has been triggered. The draggable objects in this demo have an event handler defined: onmousedown="grab(this)".


mousex,y

You should see the mouse's current coordinates constantly updating as you maneuver the mouse over the content of this page. If you don't then this script won't work at all.


grabx,y

The grab function is called when a mousedown event occurs over any of the three overlapping info boxes at the top of the page. The current mousex,y coordinate pair is copied into these variables.


orix,y

Another thing that happens when grab is called is that the top left coordinates of the dragobj are stored in orix,y.


elex,y

During the drag function, the top left coordinates are updated and stored in this variable pair. Basically, the algorithm works by keeping the difference between elex,y and orix,y to be equivalent to the difference between grabx,y and mousex,y.




A complete declaration on a div that you want to make move, might look like this (although any tag type could be made to move):
<div id="obj_id" class="css_def" onmousedown="grab(this)">.

The complete code follows. (It has been included twice in this document using PHP, so we can be sure that the embedded running code is identical to the displayed code.)


JavaScript:


var mousex = 0;
var mousey = 0;
var grabx = 0;
var graby = 0;
var orix = 0;
var oriy = 0;
var elex = 0;
var eley = 0;
var algor = 0;

var dragobj = null;

function falsefunc() { return false; } // used to block cascading events

function init()
{
  document.onmousemove = update; // update(event) implied on NS, update(null) implied on IE
  update();
}

function getMouseXY(e) // works on IE6,FF,Moz,Opera7
{ 
  if (!e) e = window.event; // works on IE, but not NS (we rely on NS passing us the event)

  if (e)
  { 
    if (e.pageX || e.pageY)
    { // this doesn't work on IE6!! (works on FF,Moz,Opera7)
      mousex = e.pageX;
      mousey = e.pageY;
      algor = '[e.pageX]';
      if (e.clientX || e.clientY) algor += ' [e.clientX] '
    }
    else if (e.clientX || e.clientY)
    { // works on IE6,FF,Moz,Opera7
      mousex = e.clientX + document.body.scrollLeft;
      mousey = e.clientY + document.body.scrollTop;
      algor = '[e.clientX]';
      if (e.pageX || e.pageY) algor += ' [e.pageX] '
    }  
  }
}

function update(e)
{
  getMouseXY(e); // NS is passing (event), while IE is passing (null)

  document.getElementById('span_browser').innerHTML = navigator.appName;
  document.getElementById('span_winevent').innerHTML = window.event ? window.event.type : '(na)';
  document.getElementById('span_autevent').innerHTML = e ? e.type : '(na)';
  document.getElementById('span_mousex').innerHTML = mousex;
  document.getElementById('span_mousey').innerHTML = mousey;
  document.getElementById('span_grabx').innerHTML = grabx;
  document.getElementById('span_graby').innerHTML = graby;
  document.getElementById('span_orix').innerHTML = orix;
  document.getElementById('span_oriy').innerHTML = oriy;
  document.getElementById('span_elex').innerHTML = elex;
  document.getElementById('span_eley').innerHTML = eley;
  document.getElementById('span_algor').innerHTML = algor;
  document.getElementById('span_dragobj').innerHTML = dragobj ? (dragobj.id ? dragobj.id : 'unnamed object') : '(null)';
}

function grab(context)
{
  document.onmousedown = falsefunc; // in NS this prevents cascading of events, thus disabling text selection
  dragobj = context;
  dragobj.style.zIndex = 10; // move it to the top
  document.onmousemove = drag;
  document.onmouseup = drop;
  grabx = mousex;
  graby = mousey;
  elex = orix = dragobj.offsetLeft;
  eley = oriy = dragobj.offsetTop;
  update();
}

function drag(e) // parameter passing is important for NS family 
{
  if (dragobj)
  {
    elex = orix + (mousex-grabx);
    eley = oriy + (mousey-graby);
    dragobj.style.position = "absolute";
    dragobj.style.left = (elex).toString(10) + 'px';
    dragobj.style.top  = (eley).toString(10) + 'px';
  }
  update(e);
  return false; // in IE this prevents cascading of events, thus text selection is disabled
}

function drop()
{
  if (dragobj)
  {
    dragobj.style.zIndex = 0;
    dragobj = null;
  }
  update();
  document.onmousemove = update;
  document.onmouseup = null;
  document.onmousedown = null;   // re-enables text selection on NS
}


This code has been tested on Win2000 and WinXP using browsers IE6, Firefox1, Mozilla1 and Opera7. It has also been tested on Safari and Firefox on the Mac and Firefox on Linux. There is an issue with Opera where text selection is not properly disabled during drag. This has an undesirable effect of selecting the background text while the objects are being dragged around (if anyone knows how to fix this, please let me know).

Programmer's note: This page has been changed since its first draft. The first draft included some details about postioning with style sheets using position:absolute versus position:relative or the default position:static. The code was modified so that this distinction is now irrelevant. Any positioning style can be used in the initial document design. Now when the objects are grabbed, their current absolute positions are recorded and their position is set to "absolute". What has also changed is the former necessity to code the elements' styled positions inline. The page designer actually has complete freedom on how they want to do this: either inlined, within a style tag or externally linked.




Dave Ratz wrote me an email in November 2005, with some comments on this code running in an XHTML document. Here is what Dave said.


Hi Paul,

Thanks for posting the javascript mouse drag and drop routines on your website. I'm adapting parts of it to a project I'm working on here in Montana.

In the spirit of good karma, I'll offer you back a slight code change that you can use to update your site if you wish. This addresses a situation where the document scroll position is not used properly by the getMouseXY function. This can happen sometimes based on the doctype of web page.

In my page I had the following doctype:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

When I ran your code, it worked fine in FF,MOZ, but in IE6 it returned XY coordinates based on the top of the window frame (scrolled down it still shows Y=0 at the top of the window). I modified your getMouseXY (comments included below) and it now returns the correct XY values regardless of the doctype.

 
function getMouseXY(e) // works on IE6,FF,Moz,Opera7
{ 
  if (!e) e = window.event; // works on IE, but not NS (we rely on NS passing us the event)
 
  if (e)
  { 
    if (e.pageX || e.pageY)
    { // this doesn't work on IE6!! (works on FF,Moz,Opera7)
      mousex = e.pageX;
      mousey = e.pageY;
      algor = '[e.pageX]';
      if (e.clientX || e.clientY) algor += ' [e.clientX] '
    }
    else if (e.clientX || e.clientY)
    { // works on IE6,FF,Moz,Opera7
      // Note: I am adding together both the "body" and "documentElement" scroll positions
      //       this lets me cover for the quirks that happen based on the "doctype" of the html page.
      //         (example: IE6 in compatibility mode or strict)
      //       Based on the different ways that IE,FF,Moz,Opera use these ScrollValues for body and documentElement
      //       it looks like they will fill EITHER ONE SCROLL VALUE OR THE OTHER, NOT BOTH 
      //         (from info at http://www.quirksmode.org/js/doctypes.html)
      mousex = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
      mousey = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
      algor = '[e.clientX]';
      if (e.pageX || e.pageY) algor += ' [e.pageX] '
    }
  }
}

Hope this is useful to you! Thanks again for your good example page :-)

Ratz
E-Mail: dratz@mt.gov

Thanks for writing Dave. I always appreciate a little extra insight.

The point raised by Dave Ratz about the interpretation of the javascript running in an XHTML page versus an HTML 4.01 page is certainly insightful. After experimenting with his suggestion and trying the code in both doctypes I have come to realize that the differences between the two are more than just the markup syntax. In fact, the discrepancy Dave notes, does affect FF as well, except for the fact that FF doesn't need the code alteration that he offers because e.pageX and e.pageY are supported in Firefox and the block of code in question never gets executed. If, as an alternative, you remove that block of code, you will see that FF also ignores any scroll applied at the browser.

Interestingly, the interpretation of the CSS is also apparently different between the two doctypes.




Valid HTML 4.01!
Valid CSS!