DHTML Jigsaw Puzzle: NN4; Dragging the Pieces | WebReference

DHTML Jigsaw Puzzle: NN4; Dragging the Pieces


The DHTML Lab Jigsaw Puzzle, Part III: NN4
positioning the puzzle pieces manually

To allow dragging of piece elements, a modified version of our Navigator drag code is used. The original Navigator code was developed in Column 6. The logic behind the puzzle adaptation was discussed in our previous column.

In addition to the piece elements, the full puzzle itself can be dragged, by grabbing it by the puzzle image.


First, our usual global tracking variables are initialized. currentX and currentY track the dragged element's position and whichEl tracks the element itself.

currentX = currentY = 0;
whichEl = null;
function grabEl(e) {
    mouseX = e.pageX;
    mouseY = e.pageY;
    for (i=0; i<document.layers.length; i++) {
        tempLayer = document.layers[i];
        if (!tempLayer.draggable) { continue }
        if ( (mouseX > tempLayer.left+tempLayer.clip.left) && 
             (mouseX < ((tempLayer.left+tempLayer.clip.left) + tempLayer.clip.width)) && 
             (mouseY > tempLayer.top+tempLayer.clip.top) && 
             (mouseY < ((tempLayer.top+tempLayer.clip.top)+ tempLayer.clip.height)) ) {
                whichEl = tempLayer;
    if (whichEl == elPuzzle && mouseY > elControls.pageY) { whichEl = null }
    if (whichEl == null) { return true }
    if (whichEl != elPuzzle &&  whichEl != activeEl) {
        activeEl = whichEl;
    currentX = mouseX;
    currentY = mouseY;
    document.onmousemove = moveEl;
    return false;

When the user presses the mouse button, the mousedown event is fired and grabEl() is called. The mousedown page location is identified and stored in mouseX and mouseY. We then loop through the document.layers array, checking first if the element in the array is draggable. If it is, we determine if the mousedown occured within its visible boundaries. If an element is identified as being the recipient of the mousedown, it is stored in the whichEl variable.

Once we have an element to drag, we see if it is the full puzzle and if the mousedown occurred over the image, that is, not over the controls. Since mouseY stores a vertical page coordinate, we compare it to the vertical page coordinate of the beginning of elControls. This is stored in the pageY property. If the mousedown occurred over the control panel, we cancel the drag by nulling whichEl.

Next, grabEl() checks for a null value for whichEl, which will exist after one of two scenarios. Either no draggable element was identified to process the mousedown, or the control panel received the event. In both cases, therefore, we return true from the function, enabling other intended recipients of the mousedown, such as a form button, to receive and process the event.

If we have a draggable element, the function continues by placing the element higher in z-order than the previous dragged element. This action occurs only when a piece is being dragged, as the full puzzle drags only when it is unbroken and is the only draggable element visible.

Finally, our present position is recorded as currentX and currentY to track movement, and we capture mousemove events and call moveEl() when one occurs. The function returns false, disabling further processing of the mousedown event.


In moveEl(), our element is moved with the normal Navigator script, but with one important addition. If the element is moved off the top or left of the page, its left and top properties become 0, keeping it within the page. This prevents the user from inadvertantly losing the piece by dragging it off the page. Since the true position of a piece is determined by the clipped left and top parts, the clipLeft and clipTop properties are used as reference.

function moveEl(e) {
    newX = e.pageX;
    newY = e.pageY;
    distanceX = (newX - currentX);
    distanceY = (newY - currentY);
    currentX = newX;
    currentY = newY;
    if (whichEl.left + whichEl.clipLeft < 0) {
        whichEl.left = 0 - whichEl.clipLeft;
    if (whichEl.top + whichEl.clipTop < 0) {
        whichEl.top = 0 - whichEl.clipTop;
    return false;


The final function, dropEl() is called on every mouseup event. If we don't have a draggable element, it immediately returns true. If we do, the mousemove captures are released, as the drag is now finished. If we have been dragging the full puzzle, no further processing is necessary, so whichEl is nulled and the function returns false. In the case of a piece drag, the function continues.

We calculate where the mouse was on the mouseup and check to see if it occured within the puzzle solve (puzzle image) area. If it did, the positioning statements are executed before whichEl is nulled and the function returns.

function dropEl(e) {
    if (whichEl == null) { return true }
    if (whichEl == elPuzzle) { whichEl = null; return false }
    dropLeft = e.pageX;
    dropTop = e.pageY;
    allowLeft = puzzLeft;
    allowRight = puzzLeft + puzzWidth;
    allowTop = puzzTop;
    allowBot = puzzTop + puzzHeight;
    if (dropLeft >= allowLeft && dropLeft <= allowRight && 
        dropTop >= allowTop && dropTop <=allowBot) {
        diffLeft = puzzLeft - whichEl.left;
        diffTop = puzzTop - whichEl.top;
        whereL = parseInt( diffLeft / pieceWidth ) * pieceWidth;
        if(isNaN(whereL)) whereL = 0;
        whereT = parseInt( diffTop / pieceHeight ) * pieceHeight;
        if(isNaN(whereT)) whereT = 0;
        modL = parseInt(diffLeft % pieceWidth);
        modT = parseInt(diffTop % pieceHeight);
        if (Math.abs(modL) > pieceWidth/2) {
            if (modL>0) {whereL+=pieceWidth} else {whereL-=pieceWidth}
        if (Math.abs(modT) > pieceHeight/2) {
            if (modT>0) {whereT+=pieceHeight} else {whereT-=pieceHeight}
        whichEl.left = (puzzLeft - whereL);
        whichEl.top = (puzzTop - whereT);
        if (whichEl.left == puzzLeft && whichEl.top == puzzTop) {
            tempEl = whichEl;
            flashTimer = setInterval("visToggle(false)",100); 
    whichEl = null;
    return false;

The positioning statements allow us to snap the piece into position, and are exactly like their IE counterparts, except for the NN naming conventions. The one addition for Navigator is the isNaN check. In certain cases, if whereL and whereT are extremely close to 0 (zero), Navigator will consider them Not a Number. Using the isNaN() function, we check for this and manually give whereL and whereT zero values.

Once the piece is snapped into position, we determine if it is in its correct position. If it is, the visToggle() function is called to flash the piece, informing the user of the correct placement.


As usual, in order for the functions to be called by the correct events, we must capture these events in our script and direct them to the functions.

    document.captureEvents(Event.MOUSEDOWN | Event.MOUSEUP); document.onmousedown = grabEl; document.onmouseup = dropEl;

We have now covered all the important aspects of the Navigator version of the puzzle code. In the following pages, we will reproduce the remaining functions. They differ from their IE counterparts in minor ways only, usually in property naming conventions.

Produced by Peter Belesis and

All Rights Reserved. Legal Notices.
Created: Nov. 27, 1997
Revised: Jan. 18, 1998

URL: http://www.webreference.com/dhtml/column10/puzzNSdrag.html