parameter | value |
navigator.appName = | |
window.event = | |
auto event = | |
algorithm = | |
dragobj = | |
mousex,y = | , |
grabx,y = | , |
orix,y = | , |
elex,y = | , |
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.
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.
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.
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".
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.
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)"
.
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.
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.
Another thing that happens when grab
is called is that the top left coordinates of the dragobj
are stored in orix,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):
.
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.)
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.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
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] ' } } }
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.