Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I set a "bounding" area for my drag-able object in javascript?

I'm making a Drag and Drop JavaScript engine. I learned how to set a bounding box as the parent element. However, now I wish to set the bounding box to any parent of any parent, or as the entire page (bound-less).

Right now my Javascript Engine looks like:

// JavaScript Document

var dragObj;

document.addEventListener("mousedown", down, false);

function down(event) {
    if(~event.target.className.search(/drag/)) {
        dragObj = makeObj(event.target);
        dragObj.element.style.zIndex="100";
        document.addEventListener("mousemove", freeMovement, false);
    }
}

function freeMovement(event) {

    if (typeof(dragObj.element.mouseup) == "undefined")
        document.addEventListener("mouseup", drop, false);
    //Prevents redundantly adding the same event handler repeatedly

    dragObj.element.style.left = Math.max(0, Math.min(event.clientX - dragObj.posX, dragObj.boundX)) + "px";
    dragObj.element.style.top = Math.max(0, Math.min(event.clientY - dragObj.posY, dragObj.boundY)) + "px";
}

function drop() {
    dragObj.element.style.zIndex="1";

    document.removeEventListener("mousemove", freeMovement, false);
    document.removeEventListener("mouseup", drop, false);
    //alert("DEBUG_DROP");
}

function makeBoundlessObj(e) {
    var obj = new Object();
    obj.element = e;

    obj.boundX = e.parentNode.offsetWidth - e.offsetWidth;
    obj.boundY = e.parentNode.offsetHeight - e.offsetHeight;

    obj.posX = event.clientX - e.offsetLeft;
    obj.posY = event.clientY - e.offsetTop;

    return obj;
}

function makeObj(e) {
    obj = new Object();
    obj.element = e;

    obj.boundX = e.parentNode.offsetWidth - e.offsetWidth;
    obj.boundY = e.parentNode.offsetHeight - e.offsetHeight;

    obj.posX = event.clientX - e.offsetLeft;
    obj.posY = event.clientY - e.offsetTop;

    var curleft = curtop = 0;
    if (e.offsetParent) {
        do {
            curleft += e.offsetLeft;
            curtop += e.offsetTop;
            //alert(e.id + ":" + e.innerHTML);
            if(~e.className.search(/bound/)) {
                obj.boundX = curleft - obj.element.offsetLeft;
                obj.boundY = curtop - obj.element.offsetTop;
                return obj;
            }

        } while (e = e.offsetParent);
    }

    return obj;
}

function findPos(obj) { // Donated by `lwburk` on StackOverflow
    var curleft = curtop = 0;
    if (obj.offsetParent) {
        do {
            curleft += obj.offsetLeft;
            curtop += obj.offsetTop;
        } while (obj = obj.offsetParent);
        return { x: curleft, y: curtop };
    }
}

My CSS is as follows:

@charset "utf-8";
/* CSS Document */


* {
    padding: 0px;
    margin: 0px;
}

.drag {
    position: absolute;
    -webkit-user-select: none;
    -moz-user-select: none;
    user-select: none;
}

.bound {
    position: relative;
}

.square {
    width: 100px;
    height: 100px;
    background: red;
    cursor:move;
}

#center {
    width: 500px;
    height: 300px;
    margin: auto;
    margin-top: 50px;
    background-color:#ccc;
    text-align: center;
    border-radius: 25px;
    -moz-border-radius: 25px;
}

#box {
    background-color: #FF3;
    height: 278px;
    border-radius: 0 0 25px 25px;
    -moz-border-radius: 0 0 25px 25px;
    opacity: 0.5;
}

And my HTML is pretty clean:

<div id="center">
    <h1>Hello World! <hr /></h1>
    <div id="box" class="bound">
        <p class="drag square"> One </p>
        <p class="drag square"> Two </p>
    </div>
</div>

I've attempted to make the proper functions multiple times. I'll give one that I've made which doesn't work, and I'll list why:

  1. If it doesn't have bounds, I set the default bounds as the parent element (because I don't know how to set bounds as the entire page)

  2. If one of the parent elements IS a bound, then I am not setting the bound coordinates correctly (again, I don't know how)

Oh, and I set the bounds while I create the drag_object.

JavaScript creation function:

function makeObj(e) {
    var obj = new Object();
    obj.element = e;

    obj.boundX = e.parentNode.offsetWidth - e.offsetWidth;
    obj.boundY = e.parentNode.offsetHeight - e.offsetHeight;

    obj.posX = event.clientX - e.offsetLeft;
    obj.posY = event.clientY - e.offsetTop;

    var curleft = curtop = 0;
    if (e.offsetParent) {
        do {
            curleft += e.offsetLeft;
            curtop += e.offsetTop;
            //alert(e.id + ":" + e.innerHTML);
            if(~e.className.search(/bound/)) {
                obj.boundX = curleft - obj.element.offsetLeft;
                obj.boundY = curtop - obj.element.offsetTop;
                return obj;
            }

        } while (e = e.offsetParent);
    }

    return obj;
}

What is the correct math for setting the bounding box and why? Can I get rid of the position: relative in the .bound class? Can I make .drag class not position: absolute? I know all of these things will probably greatly affect how the bounding function is written. If I had to choose between having the .drag class or the .bound class not need a certain type of position, I would choose that the .bound class be set to any kind of positioning.

Thank you all for reading and helping! It means a lot to me; I'm a full time (boarding) high school student with very little free time =/

EDIT:

I should note that I'm on my tenth day of learning Javascript- or fifteenth-hour depending on how you look at it, and I would like to learn the language before I start using libraries like jQuery. This engine is an academic exercise I've made for myself for the sake of knowledge and learning the language =]

like image 357
Dbz Avatar asked Feb 27 '11 01:02

Dbz


2 Answers

The first thing I noticed is that you didn't have a minimum boundary. You'll need that in order to enforce the upper AND lower bound.

What is the correct math for setting the bounding box and why?

First thing is that dragObj needs to account for both boundaries (applies to position: absolute):

// parentNode is our bounding box
// the minimum boundary is based on the top left corner of our container
obj.minBoundX = e.parentNode.offsetLeft;
obj.minBoundY = e.parentNode.offsetTop;

// the maximum is the bottom right corner of the container
// or.. the top left (x,y) + the height and width (h,y) - the size of the square
obj.maxBoundX = obj.minBoundX + e.parentNode.offsetWidth - e.offsetWidth;
obj.maxBoundY = obj.minBoundY + e.parentNode.offsetHeight - e.offsetHeight;

Enforcing the boundaries is a simple update to freeMovement:

dragObj.element.style.left = Math.max(dragObj.minBoundX, Math.min(event.clientX - dragObj.posX, dragObj.maxBoundX)) + "px";
dragObj.element.style.top = Math.max(dragObj.minBoundY, Math.min(event.clientY - dragObj.posY, dragObj.maxBoundY)) + "px";

Can I get rid of the position: relative in the .bound class? Yup.

Can I make .drag class not position: absolute? Yup. You'll just want to change your positions to be relative and your calculations to account for this. For example, your minimum bound will now be 0.

// parentNode is our bounding box
// the minimum boundary is based on the top left corner of our container
obj.minBoundX = 0;
obj.minBoundY = 0;

Here is the JSFiddle for the position:absolute version: http://jsfiddle.net/feWcQ/ (works with Firefox 4). I also added two tiny boxes that show your boundaries. Hopefully my answer helps you!

like image 111
Kit Menke Avatar answered Oct 06 '22 00:10

Kit Menke


So, before I take a stab, I'd strongly recommend the book by OReilly, "Definitive Guide to JavaScript". I learned a ton in there (and subsequently forgot it when I didnt have to use it every day).

So there's a lot of ways to skin this cat, but I the hardest part is to get the positions of the relevant things in a cross-browser way without using a library. I don't actually recall the exact syntax (so I won't try) but basically what you'll need to do is find the bounding box's position and the dragged element's position using this formula: http://blog.firetree.net/2005/07/04/javascript-find-position/ and then account for element widths and so on to stay inside the boxes.

like image 22
Paul Avatar answered Oct 05 '22 23:10

Paul