-->
Bookmark and Share

Generic Drag

See the demo page for the finished version of the code.

The Drag Process

The basic drag process consists of three steps: Initialize the drag when the mouse button is first depressed, move the target element as the mouse is moved and, finally, stop the drag when the mouse button is released.

Initializing the Drag

The dragStart() function is called by the onmousedown event handler of some element, as described earlier. It first determines the target element for the drag.

function dragStart(event, id) {

  var el;
  var x, y;

  // If an element id was given, find it. Otherwise use the element being
  // clicked on.

  if (id)
    dragObj.elNode = document.getElementById(id);
  else {
    if (browser.isIE)
      dragObj.elNode = window.event.srcElement;
    if (browser.isNS)
      dragObj.elNode = event.target;

    // If this is a text node, use its parent element.

    if (dragObj.elNode.nodeType == 3)
      dragObj.elNode = dragObj.elNode.parentNode;
  }

If an id was given, the script uses document.getElementById() to locate the element. Otherwise it uses the event object to determine which element the mouse is over and defaults to it. Either way, the target element is assigned to dragObj.elNode.

See the Introduction to the Document Object Model for details on node types.

Once special situation must checked for. If the mouse is over text, the target of the mousedown event will be a text node, instead of an element node. Text nodes contain just that, textual data, and have no style properties, like position. Instead, the appearance and position of the text is determined by the style properties of the element they belong to.

So if the nodeType of the event's target node happens to be TEXT_NODE (= 3), the script takes its parentNode instead. The parent of a text node is always the element that contains the text.

Browser Compatibiliy

Netscape defines constants in the DOM for each node type. For example, document.ELEMENT_NODE returns a value of 1. Internet Explorer does not provide these, so the actual numeric value is used in the script instead.

The next step is to find the current position of the mouse pointer. These starting pixel coordinates will be used later to compare with the mouse position during the drag.

  // Get cursor position with respect to the page.

  if (browser.isIE) {
    x = window.event.clientX + document.documentElement.scrollLeft
      + document.body.scrollLeft;
    y = window.event.clientY + document.documentElement.scrollTop
      + document.body.scrollTop;
  }
  if (browser.isNS) {
    x = event.clientX + window.scrollX;
    y = event.clientY + window.scrollY;
  }

Note that the event object's clientX and clientY values are relative to the viewport of the browser window, not to the page. Just in case the window should scroll during the drag, we want to get the cursor position relative to the page itself. So the current scroll offsets are added.

You can see the difference for yourself by scrolling this page and clicking anywhere on it. The clientX and clientY values are displayed in the browser status window along with the calculated page offset values.

Browser Compatibility

Internet Explorer has scroll offset properties in both the document.body object and the document.documentElement object. But IE 5 only updates the pair in document.body while the pair in document.documentElement always return zero. IE 6 does just the opposite.

The above code just adds the scroll offset value from both objects, so it will get the correct value regardless of version.

Netscape stores the page scroll offset values as properties of the window object.

Next, the function finds the target element's current position, using the numeric portion of its left and top style values. These are saved in dragObj along with the mouse coordinates.

  // Save starting positions of cursor and element.

  dragObj.cursorStartX = x;
  dragObj.cursorStartY = y;
  dragObj.elStartLeft  = parseInt(dragObj.elNode.style.left, 10);
  dragObj.elStartTop   = parseInt(dragObj.elNode.style.top,  10);

  if (isNaN(dragObj.elStartLeft)) dragObj.elStartLeft = 0;
  if (isNaN(dragObj.elStartTop))  dragObj.elStartTop  = 0;

Note here that if left or top were not set using an inline style on the element, the code assumes a value of zero.

To bring the element to the top of the stacking order, the script updates its zIndex style, after first incrementing the value of dragObj.zIndex.

  // Update element's z-index.

  dragObj.elNode.style.zIndex = ++dragObj.zIndex;

The last step is to set up capture of the mousemove and mouseup events on the entire page, assigning the handler functions dragGo() and dragStop() respectively.

  // Capture mousemove and mouseup events on the page.

  if (browser.isIE) {
    document.attachEvent("onmousemove", dragGo);
    document.attachEvent("onmouseup",   dragStop);
    window.event.cancelBubble = true;
    window.event.returnValue = false;
  }
  if (browser.isNS) {
    document.addEventListener("mousemove", dragGo,   true);
    document.addEventListener("mouseup",   dragStop, true);
    event.preventDefault();
  }

The script then cancels the current event bubble and any default action. Normally, if you drag the mouse over a page the browser will highlight a section of text or other content. Stopping the event like this prevents that action during the drag.