Drag and Drop Concepts

Drag and drop scripts are entirely based around the mouse events. Just as you can capture keyboard events, you can capture mouse events like onMouseDown, onMouseUp, and onMouseMove. For each of these, you can obtain the location of the mouse and use those coordinates to move a layer. This lesson will show step by step how to make one layer draggable. The next lesson will show how to make any number of layers draggable with a generic Drag Object.

Initialize Mouse Events:

Each of onMouseDown, onMouseUp, and onMouseMove are initialized the same way. Here's the set up I like to use:


function init() {	// called from BODY onLoad

	document.onmousedown = mouseDown

	document.onmousemove = mouseMove

	document.onmouseup = mouseUp

	if (ns4) document.captureEvents(Event.MOUSEDOWN | Event.MOUSEMOVE | Event.MOUSEUP)

}



function mouseDown(e) {

}

function mouseMove(e) {

}

function mouseUp(e) {

}

The function names can be whatever you want, but I find it easiest to understand by using just mouseDown(e), mouseMove(e), and mouseUp(e).

For Netscape, the "e's" in each function represent the built-in Event object. It is how it will obtain the location of the mouse:


var x = e.pageX

var y = e.pageY

IE will ignore the e's because it uses a slightly different system for capturing mouse events. In IE, the window contains a event object which you can access using window.event (which is synonymouse with just event). The window.event object contains the properties x and y which represent the location of where the mouse event occured:


var x = event.x

var y = event.y

Revision: Before I did not take into account scrolling in IE, read the following to correct this issue.

To be specific those values reflect where in IE's browser window the mouse event occured - it does not necessarily relect where on the document has been clicked. If you scroll down the window.event.y value isn't in synch with the document, so we have to account for that discrepency ourselves. You add the amount that the document has been scrolled by using document.body.scrollTop.


var x = event.x+document.body.scrollLeft

var y = event.y+document.body.scrollTop

You can then put the code for both browsers into one small chunk of code:


if (ns4) {var x=e.pageX; var y=e.pageY}

if (ie4) {var x=event.x; var y=event.y+document.body.scrollTop}

Those lines can then be inserted into each of the mouse functions:


function mouseDown(e) {

	if (ns4) {var x=e.pageX; var y=e.pageY}

	if (ie4) {var x=event.x; var y=event.y+document.body.scrollTop}

}

function mouseMove(e) {

	if (ns4) {var x=e.pageX; var y=e.pageY}

	if (ie4) {var x=event.x; var y=event.y+document.body.scrollTop}

}

function mouseUp(e) {

	if (ns4) {var x=e.pageX; var y=e.pageY}

	if (ie4) {var x=event.x; var y=event.y+document.body.scrollTop}

}

Now when any of the events occur we can work with the x and y variables (the current location of the mouse) and do some neat things with them.

In Netscape, problems occur when you click the right mouse button - it interfers with the drag and drop process, so I usually like to do to a check of which mouse button was clicked by using e.which - (1) means that the left button was clicked:


function mouseDown(e) {

	if ((ns4 && e.which == 1) || ie4) {

		if (ns4) {var x=e.pageX; var y=e.pageY}

		if (ie4) {var x=event.x; var y=event.y+document.body.scrollTop}

	}

}

Using mouseDown():

Right now we're going to do a very simple action - when someone clicks the mouse, move a layer to the same location as the mouse. First we need to initialize a layer (I'll use the old pointer variable method):


if (ns4) {

	dragObj = document.squareDiv

	dragObj.xpos = dragObj.left

	dragObj.ypos = dragObj.top

}

if (ie4) {

	dragObj = squareDiv.style

	dragObj.xpos = dragObj.pixelLeft

	dragObj.ypos = dragObj.pixelTop

}

For this lesson, the DIV tag will look like this:


<STYLE TYPE="text/css">

<!--

#squareDiv {position:absolute; left:100; top:100; width:50; height:50; clip:rect(0,50,50,0); background-color:blue; layer-background-color:blue;}

-->

</STYLE>

<DIV ID="squareDiv"></DIV>

Then in the mouseDown() function we can use the x and y variables to move the layer to those values:


function mouseDown(e) {

	if ((ns4 && e.which == 1) || ie4) {

		if (ns4) {var x=e.pageX; var y=e.pageY}

		if (ie4) {var x=event.x; var y=event.y+document.body.scrollTop}

		dragObj.xpos = x

		dragObj.ypos = y

		dragObj.left = dragObj.xpos

		dragObj.top = dragObj.ypos

	}

}

Also, just for fun, I'm going to show the location of the mouse in the status bar:


function mouseMove(e) {

	if (ns4) {var x=e.pageX; var y=e.pageY}

	if (ie4) {var x=event.x; var y=event.y+document.body.scrollTop}

	status = "x:"+x+" y:"+y

}

View dragdrop1.html to see this example

Using mouseMove():

Unlike mouseDown() and mouseUp(), the mouseMove() function is always active whenever the mouse is moved. But we don't always want to do things when we move the mouse. In the case of drag and drop, we only want to do something after we click something. To have more control over the mouseMove event we can use an active variable like dragActive:


dragActive = false     // initially it is not active

Then in the mouseDown() function, instead of moving the layer to the location of the mouse, I'm going to just set the mouseMoveActive variable to 1 (true):


function mouseDown(e) {

	if ((ns4 && e.which == 1) || ie4) {

		if (ns4) {var x=e.pageX; var y=e.pageY}

		if (ie4) {var x=event.x; var y=event.y+document.body.scrollTop}

		dragActive = true

	}

}

Then I'm going to let mouseMove do all the work. I'll make it so that each time the mouse is moved, the layer is going to move to where the mouse is:


function mouseMove(e) {

	if (ns4) {var x=e.pageX; var y=e.pageY}

	if (ie4) {var x=event.x; var y=event.y+document.body.scrollTop}

	status = "x:"+x+" y:"+y

	if (dragActive) {

		dragObj.xpos = x

		dragObj.ypos = y

		dragObj.left = dragObj.xpos

		dragObj.top = dragObj.ypos

		return false

	}

}

I added one extra command - return false. This is very important - it is to avoid a Netscape problem which occurs when you want to put an image in the CSS-layer. If you don't have return false the drag and drop action gets broken.

View dragdrop2.html to see this example

Using mouseUp():

In that last example, there's no way to "drop" the square once we've clicked. To change that I'm going to use mouseUp to set the mouseMoveActive variable to 0 which will disable the mouseMove action:


function mouseUp(e) {

	if (ns4) {var x=e.pageX; var y=e.pageY}

	if (ie4) {var x=event.x; var y=event.y+document.body.scrollTop}

	dragActive = false

}

Another thing we can do to make the script better is change how we move the layer in the mouseMove() function. Instead of moving the layer directly to where the layer is, we can change it so that the layer is moved according to where on the layer the mouse was clicked.

So in the mouseDown() function we'll have to capture the difference between where the mouse was clicked and the location of the layer:


function mouseDown(e) {

	if ((ns4 && e.which == 1) || ie4) {

		if (ns4) {var x=e.pageX; var y=e.pageY}

		if (ie4) {var x=event.x; var y=event.y+document.body.scrollTop}

		dragClickX = x-dragObj.xpos

		dragClickY = y-dragObj.ypos

		dragActive = true

	}

}

Then the mouseMove() function can be updated to include these values when we move the layer:


function mouseMove(e) {

	if (ns4) {var x=e.pageX; var y=e.pageY}

	if (ie4) {var x=event.x; var y=event.y+document.body.scrollTop}

	if (dragActive) {

		dragObj.xpos = x-dragClickX

		dragObj.ypos = y-dragClickY

		dragObj.left = dragObj.xpos

		dragObj.top = dragObj.ypos

		return false

	}

}

View dragdrop3.html to view see example

Finishing it up:

We're pretty close now. The only fault in the script is that we never determine when to activate the drag and drop sequence. Right now it always drags no matter where on the screen we click. The usual functionality is that you only drag the layer when you've clicked directly on top of it. You can determine if you've clicked on the layer by doing another check in the mouseDown function.

The most common way to approach this is to compare the location of mouse with the location of the layer. You need to obtain each edge of the layer (left, right, top, and bottom). The left and top are easy - dragObj.left and dragObj.top respectively. But for the right and bottom we have to take into consideration the width and height of the layer.

We can easily tack on a few more properties to our pointer variables capture the width and height of the layer.

In Netscape, the width and height are based on the clip width and clip height values:


document.layername.clip.width

document.layername.clip.height

In IE, you have to use pixelWidth and pixelHeight


layername.style.pixelWidth

layername.style.pixelHeight

So our initialization code can be altered to include these values in the properties w and h:


if (ns4) {

	dragObj = document.squareDiv

	dragObj.xpos = dragObj.left

	dragObj.ypos = dragObj.top

	dragObj.w = dragObj.clip.width

	dragObj.h = dragObj.clip.width

}

if (ie4) {

	dragObj = squareDiv.style

	dragObj.xpos = dragObj.pixelLeft

	dragObj.ypos = dragObj.pixelTop

	dragObj.w = dragObj.pixelWidth

	dragObj.h = dragObj.pixelHeight

}

So using each of these properties we can set up one big "if" statement to check if the user has clicked within the boundaries of our layer:


if (x>=dragObj.xpos && x<=dragObj.xpos+dragObj.w && y>=dragObj.ypos && y<=dragObj.ypos+dragObj.h) {

This line is inserted into the mouseDown() function to make sure that we activate the drag sequence only when the layer is clicked:


function mouseDown(e) {

	if ((ns4 && e.which == 1) || ie4) {

		if (ns4) {var x=e.pageX; var y=e.pageY}

		if (ie4) {var x=event.x; var y=event.y+document.body.scrollTop}

		if (x>=dragObj.xpos && x<=dragObj.xpos+dragObj.w && y>=dragObj.ypos && y<=dragObj.ypos+dragObj.h) {

			dragClickX = x-dragObj.xpos

			dragClickY = y-dragObj.ypos

			dragActive = true

			return false

		}

	}

}

You must put a return false in the onMouseDown when the drag sequence is started. This will avoid problems on the Mac. Since Mac's have one butten, Netscape will pop up a command list when the mouse button is held. By returning false it cancels that event.

The complete Drag and Drop script:


// Drag and Drop Script

// Copyright 1998 Dan Steinman

// Available at the Dynamic Duo (http://www.dansteinman.com/dynduo/)

// May 21, 1998.

// In order to use this code you must keep this disclaimer



ns4 = (document.layers)? true:false

ie4 = (document.all)? true:false



function init() {

	if (ns4) {

		dragObj = document.squareDiv

		dragObj.xpos = dragObj.left

		dragObj.ypos = dragObj.top

		dragObj.w = dragObj.clip.width

		dragObj.h = dragObj.clip.width

	}

	if (ie4) {

		dragObj = squareDiv.style

		dragObj.xpos = dragObj.pixelLeft

		dragObj.ypos = dragObj.pixelTop

		dragObj.w = dragObj.pixelWidth

		dragObj.h = dragObj.pixelHeight

	}



	dragActive = false

		

	document.onmousedown = mouseDown

	document.onmousemove = mouseMove

	document.onmouseup = mouseUp

	if (ns4) document.captureEvents(Event.MOUSEDOWN | Event.MOUSEMOVE | Event.MOUSEUP)

}



function mouseDown(e) {

	if ((ns4 && e.which == 1) || ie4) {

		if (ns4) {var x=e.pageX; var y=e.pageY}

		if (ie4) {var x=event.x; var y=event.y+document.body.scrollTop}

		if (x>=dragObj.xpos && x<=dragObj.xpos+dragObj.w && y>=dragObj.ypos && y<=dragObj.ypos+dragObj.h) {

			dragClickX = x-dragObj.xpos

			dragClickY = y-dragObj.ypos

			dragActive = true

			return false

		}

	}

}



function mouseMove(e) {

	if (ns4) {var x=e.pageX; var y=e.pageY}

	if (ie4) {var x=event.x; var y=event.y+document.body.scrollTop}

	if (dragActive) {

		dragObj.xpos = x-dragClickX

		dragObj.ypos = y-dragClickY

		dragObj.left = dragObj.xpos

		dragObj.top = dragObj.ypos

		return false

	}

}



function mouseUp(e) {

	if (ns4) {var x=e.pageX; var y=e.pageY}

	if (ie4) {var x=event.x; var y=event.y+document.body.scrollTop}

	dragActive = false

}

View dragdrop4.html to see the complete drag and drop example

Home Next Lesson: Drag Object
copyright 1998 Dan Steinman