19 September 2008

A simple Javascript function to move elements

The proper way to drag divs or images or other html elements using javascript is, of course, to use a well-known and established framework for doing it - scriptaculous, for example. Even better would be to use Firefox's fancy new drag/drop handlers (as soon as they work, that is). But if you suffer, as I do, from NIH Syndrome, here's a roll-your-own version.

Just so we agree on what dragging is: the user clicks on an object, and while moving the mouse, the object follows the mouse around, until the user releases the mouse button. In the version below, we distinguish between the target (the thing to be moved), and the handle (the thing the user clicks on in order to move the target. A titlebar, for example).

The following has been tested on FF3 and almost certainly won't work on Exploder. But there are plenty of resources out there on hacking IE until it works.

Enough talking, here's the good stuff:

var makeMovable = function(target, handle) {
  target = $(target);

  $(handle).onmousedown = function(event) {
    var initialXOffset = target.offsetLeft - event.pageX;
    var initialYOffset = target.offsetTop - event.pageY;

    document.onmousemove = function(event) {
      target.style.left = event.pageX + initialXOffset;
      target.style.top = event.pageY + initialYOffset; 
    }

    document.onmouseup = function() {
      document.onmousemove = null;
      document.onmouseup = null;
    }

    return false;
  }
}

Once your HTML elements are defined, all you need do is call makeMovable with the relevant params:

<div id="myContent" style="position:absolute;left:100px;top:100px;">
  <img id="myHandle" src="/images/move_handle.png" />
  you can move this text by dragging the handle
</div>

<script type="text/javascript">
      makeMovable("myContent", "myHandle");
</script>

(Of course, you might prefer to take an unobtrusive approach and have all movable elements wired up using identifiers and class names. Exercise for reader).

So here's what's going on. The principle is simple: makeMovable sets a "mousedown" listener on the handle - since that's what we're waiting for our user to click on. Once mousedown is triggered, we calculate how far away the mouse click was from the corner of the target. This gets stored in initialXOffset and initialYOffset. The goal now is just to make sure that the target is always this distance from the mouse, as the mouse moves around.

So we need a mousemove listener. Curiously, mousemove has to be on document - you would think it should go on the handle instead because the mouse is necessarily over the handle when we get the initial mousedown - but unless it's document.onmousemove, Firefox gets all confused and thinks you're trying to select text at the same time as moving stuff around.

The mousemove listener does the real work: it sets the top and left style elements of the target so as to maintain the original offset from the mouse position. Note that the original style declaration of the target div includes position:absolute. Without this, the top and left style elements are completely ignored, and you won't be able to move anything.

Remember to return false; at the end of onmousedown. Without this line, it mostly works, but FF also initiates its internal drag handler as well as yours, resulting in angry users tearing at their screens in frustration, and the resulting drag behaviour isn't at all intuitive or pleasant.

The last word goes to onmouseup: once the user lets go of the mouse, we should restore document.onmousemove and document.onmouseup to whatever they were before. I'm being lazy here and setting them to null. Umm ... exercise for reader ...

If you're not using Prototype, replace $(...) with document.getElementById(...).

You can write similar code for resizing an element - you would adjust the width and height style elements instead of top and left.

Leave a comment if this works for you, or more importantly, if it doesn't. Leave a comment even if there's some other javascript question bugging you. I'm curious.

5 comments:

  1. Interesting ....

    How about a second version of this that give pageflake type functionality, when the mouse button is lifted maybe it could "snap in to an area" like on say igoogle (www.google.com/ig).

    .....

    I'm here looking for code to reposition a silverlight element (renders as an object tag).

    I have written a content management system that uses a tree down the left hand side of the page, I want a script to allow the user to "hover over" a tab that then fires up a slide out of the tree control, any thoughts on that ?

    ReplyDelete
  2. @wardy, you might be interested in http://www.moonkiki.com/imoogle/ ... apparently mootools does what you're looking for.

    ReplyDelete
  3. @conan, I have elaborated on your code a little to make IE "Exploder" actually work. Here is a link: http://www.peteamundson.com/home/2009/5/22/make-movable.html

    ReplyDelete
  4. Hi. In my short search for an easy way to much stuff on a webpage, this sure is the most simple one I found.
    Do you know of a simple way to make the moved element stay at it's new place (after a reload of the page)? It's probably not as simple as I hope it to be, but you never know.

    ReplyDelete
  5. @Michaël, I have used cookies to do this, it's not so difficult. Adapt onmousemove to store the object's position in a cookie, something like setCookie(id, Object.toJSON({top: top, left: left}), and on reload reset the positions of each object you care about from their cookie if present.

    ReplyDelete