1

I've searched for an answer for this and couldn't find it... so hopefully someone can help me out. I'm trying to create a draggable div (using jquery ui) that is contained to a circle (and not a square). Right now, I've got the containment setting linked to it's parent div, but no matter what I try... it still allows the draggable element into the corners of the parent square div. I've tried setting the border-radius of the parent div so that it's circular and not square (but this might just be a css thing, and not effecting the actual div shape). Does anyone know how to constrain a draggable div to a circle? Thanks, Andy

var containment_radius = 50;

$(this.dragobject).draggable({ 
    drag: function() {
        var position = $(this.dragobject).position();
        var result = limit(position.left, position.top, containment_radius, containment_radius);
        if (!result.limit) {
            $(this.dragobject).x = result.x + "px";
            $(this.dragobject).y = result.y + "px";
        }
        console.log(result);
    } 
});

function limit(x, y, x1, y1) {
    var dist = distance([x, y], [x1, y1]);
    if (dist <= containment_radius) {
        return {x: x, y: y};
    } else {
        return {limit: true};
    }
}

function distance(dot1, dot2) {
    var x1 = dot1[0],
    y1 = dot1[1],
    x2 = dot2[0],
    y2 = dot2[1];
    return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}
andyopayne
  • 1,348
  • 3
  • 24
  • 49
  • You'd have to grab the cursor position and do the math yourself. – Diodeus - James MacFarlane Jan 30 '13 at 19:59
  • Ok. Thanks. I was afraid of that. I'll give it a shot, but if anyone has a short example, it would be very much appreciated. – andyopayne Jan 30 '13 at 20:19
  • This thread might do the trick. http://stackoverflow.com/questions/8515900/how-to-constrain-movement-within-the-area-of-a-circle – andyopayne Jan 30 '13 at 20:24
  • So, I've cobbled this code together. When I check the log results, it does seem to report true when it's outside the circle containment, or the x,y position when inside the containment... but the location of the draggable object is never updated... meaning it can still be dragged outside of the circle containment. Thoughts? – andyopayne Jan 30 '13 at 21:13

3 Answers3

1

This solution follows Luke H's suggestion of updating the ui.position. It's simple to implement (no need to extend the stock draggable widget) but I don't like it because the radius must be accessed from outside the draggable instead of being taken from the containment itself.

var radius = 128;
$('.color-picker-map-selector').draggable({
    containment: '.map',
    drag: function( event, ui ) {
        var x = ui.position.left - radius,
            y = radius - ui.position.top,
            h = Math.sqrt(x*x + y*y);
        if (Math.floor(h) > radius) {
            ui.position.top = radius - Math.round(radius * y / h);
            ui.position.left = Math.round(radius * x / h) + radius;
        }
    }
});

There is no need for elaborate math functions such as sine and cosine as many examples use.

My preferred solution is to extend the widget by adding a shape option ('rectangular, circular') and override the internal _generatePosition function. It uses the width and height of the container to find the center and it will constrain the draggable to an ellipse if the container is not square.

// add shape option to stock draggable
$.widget( "ui.draggable", $.ui.draggable, {
    options: {
        shape: 'rectangular'
    },
    _generatePosition( event, constrainPosition ) {
        var position = this._super( event, constrainPosition );
        if (this.options.shape != 'circular') return position;
        if ( constrainPosition ) {
            if ( this.containment ) {
                if ( this.relativeContainer ) {
                    co = this.relativeContainer.offset();
                    containment = [
                        this.containment[ 0 ] + co.left,
                        this.containment[ 1 ] + co.top,
                        this.containment[ 2 ] + co.left,
                        this.containment[ 3 ] + co.top
                    ];
                } else {
                    containment = this.containment;
                }
                var xRadius = Math.floor((containment[ 2 ] - containment[ 0 ]) / 2),
                    yRadius = Math.floor((containment[ 3 ] - containment[ 1 ]) / 2),
                    x = position.left - xRadius,
                    y = yRadius - position.top,
                    h = Math.sqrt(x*x + y*y);
                if (Math.floor(h) > Math.min(xRadius, yRadius)) {
                    var ye = Math.round(yRadius * y / h),
                    xe = Math.round(xRadius * x / h);
                    if ((Math.abs(y) > Math.abs(ye)) || (Math.abs(x) > Math.abs(xe))) {
                        position.left = xe + xRadius;
                        position.top = yRadius - ye;
                    }
                }
            }
        }
        return position;
    }
});

There is some small issue should the jQuery UI guys change the name and usage of the _generatePosition but it would be nice if they were to change the internals of the function so it called some helper function, say _constrain() which would simplify this code significantly.

Also note this solution doesn't work with grid alignment but that can be accomplished with a little additional slight of code.

Cheers!

Borgboy
  • 640
  • 5
  • 6
0

See this answer which shows how to use the drag event to constrain. This is done by updating the ui.position variable within the drag event handler.

Community
  • 1
  • 1
Luke H
  • 3,125
  • 27
  • 31
0

Duopixel replied to this exact question, with an example contained to a circle, not a wave as andyopayne suggested here.

How to constrain movement within the area of a circle

Here's the example http://jsfiddle.net/7Asn6/

The trick is calculating the distance from the coordinates of initial and current drag positions, with

Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));

and then calculating the new x and y coordinates with

x: Math.cos(radians) * canvas.radius + canvas.center[0], y: Math.sin(radians) * canvas.radius + canvas.center[1]

in a callback for drag:

Community
  • 1
  • 1
sergio
  • 997
  • 1
  • 11
  • 15