Drag div by leading point (like a boat in water)
Asked Answered
M

2

8

I'm try to get a div to drag much like a boat through water, but I'm having some trouble getting the rotation right.

Here is what I have so far:
jsFiddle

JS

var start, stop;
$('#canoe').draggable({
    containment: '#board',
    cursor: 'none',
    cursorAt: {
        top: 5
    },
    drag: function (event, ui) {
        start = ui.position.left;
        setTimeout(function () {
            stop = ui.position.left;
        }, 150);
        $('#canoe').css({
            'transform': 'rotate(' + (start - stop) + 'deg)'
        });
    }
});

CSS

#board {
    height:100%;
    width:100%;
    background:blue;
}
#canoe {
    background: #fff;
    border-radius:100% 100% 100% 100%;
    height:60px;
    width:10px;
    position:absolute;
    left:50%;
    bottom:0;
    transform-origin:top center;
    transition: transform .2s;
}

HTML

<div id="board">
    <div id="canoe">A</div>
</div>

Is there a better way to get the rotation set up so that the front of the boat is always leading, even with a 360deg rotation?

Additional Context: I'm working on a Basic Game

Bounty update: I need the "boat" to be able to be dragged in a circle in one continuous motion without flipping/switching the rotation direction.

Major answered 21/6, 2013 at 16:20 Comment(2)
You probably need to set the rotation inside the setTimeout - jsfiddle.net/unuCD/9Submaxillary
@Vega that doesn't seem to do it jsfiddle.net/apaul34208/unuCD/10 ... unless there's more to it that I'm missing?Major
T
1

It's a bit more complicated, but here's how I'd do it :

var save = false, timer;

$('#canoe').draggable({
    containment: '#board',
    cursor: 'none',
    cursorAt: {
        top: 5
    },
    drag: function (event, ui) {
        if ( !save ) save = ui.offset;
        var canoe    = $('#canoe'),
            center_x = save.left + 5,
            center_y = save.top + 30,
            radians  = Math.atan2(event.pageX - center_x, event.pageY - center_y),
            degree   = (radians * (180 / Math.PI) * -1) + 180,
            time     = Math.abs(ui.offset.top-save.top) + Math.abs(ui.offset.left-save.left);

        canoe.css({
            '-moz-transform'    : 'rotate('+degree+'deg)',
            '-webkit-transform' : 'rotate('+degree+'deg)',
            '-o-transform'      : 'rotate('+degree+'deg)',
            '-ms-transform'     : 'rotate('+degree+'deg)'
        });

        timer = setTimeout(function() {
            clearTimeout(timer);
            save = ui.offset;
        }, Math.abs( time-300 ) + 400 );
    }
});

FIDDLE

It compares the current mouse position to where the center of the canoe was some given time ago.
The time is set based on how fast the mouse moves, as slower movements will need a longer timeout etc.

It's also a good idea to clear the timeouts so they don't build up, even if it wasn't really issue when I tested this, and the use of Math.abs ensures it's always a positive integer.

I added a few more browser prefixes to the CSS.

Tumbledown answered 26/6, 2013 at 12:30 Comment(6)
This seems closer to what I'm after, but it still seems to have issues executing a circular motion.Major
@apaul34208 - Are you using Firefox ?Tumbledown
Try Chrome or IE, seems Firefox has issues! Tried figuring out what the problem in FF is, but it just won't do what I tell it.Tumbledown
I worked out the problem, I had transition: transform .2s; set on #canoe which was gumming up the works in FF. Unfortunately without it seems a bit twitchy. Do you know of any other way to smooth things out?Major
Increasing the timeouts a little would probably make it smoother but at the same time a little "slower", see if this helps any -> jsfiddle.net/unuCD/26Tumbledown
@Tumbledown if you're now using JQuery versions prior to ver 1.7.2, it will add the css prefixes for you, so you may just use: transform' : 'rotate('+degree+'deg)' and other browser specific lines will be handled automatically.Concessive
I
2

You need to:

  • Store position each time it changes
  • At change, calculate the angle of the line between said positions
  • Save last position

http://jsfiddle.net/AstDerek/799Tp/

Movement doesn't look soft but is closer to what you want.

If you want to simulate water drag, you'd need to reduce the angle change by some factor, then use some time interval or similar to continue movement after dragging has ended, until the angle of the ship matches the angle it should have, or a new drag event starts.

Introject answered 21/6, 2013 at 16:56 Comment(7)
This seems to swing around a little wild. Any way to reduce the sensitivity?Major
Multiply the angle for some value < 1Introject
In this example I managed to reduce the swing around a litte bitOphthalmic
@AstDerek I've run into another issue with this solution. Try dragging the boat in a circle in one continuous motion. It will flip at a certain pointMajor
I've been thinking a lot. The script needs to remember the past angle as well and define the direction of the rotation from there, made a test to clarify the idea but I'm still a bit confused :PIntroject
One suggestion : instead of storing the last position, store the last x positions (try it with 2, 3, 4...), and make an average of these + currentEliga
Hmmm... somewhat dampens the glitches, but not perfect jsfiddle.net/799Tp/2Eliga
T
1

It's a bit more complicated, but here's how I'd do it :

var save = false, timer;

$('#canoe').draggable({
    containment: '#board',
    cursor: 'none',
    cursorAt: {
        top: 5
    },
    drag: function (event, ui) {
        if ( !save ) save = ui.offset;
        var canoe    = $('#canoe'),
            center_x = save.left + 5,
            center_y = save.top + 30,
            radians  = Math.atan2(event.pageX - center_x, event.pageY - center_y),
            degree   = (radians * (180 / Math.PI) * -1) + 180,
            time     = Math.abs(ui.offset.top-save.top) + Math.abs(ui.offset.left-save.left);

        canoe.css({
            '-moz-transform'    : 'rotate('+degree+'deg)',
            '-webkit-transform' : 'rotate('+degree+'deg)',
            '-o-transform'      : 'rotate('+degree+'deg)',
            '-ms-transform'     : 'rotate('+degree+'deg)'
        });

        timer = setTimeout(function() {
            clearTimeout(timer);
            save = ui.offset;
        }, Math.abs( time-300 ) + 400 );
    }
});

FIDDLE

It compares the current mouse position to where the center of the canoe was some given time ago.
The time is set based on how fast the mouse moves, as slower movements will need a longer timeout etc.

It's also a good idea to clear the timeouts so they don't build up, even if it wasn't really issue when I tested this, and the use of Math.abs ensures it's always a positive integer.

I added a few more browser prefixes to the CSS.

Tumbledown answered 26/6, 2013 at 12:30 Comment(6)
This seems closer to what I'm after, but it still seems to have issues executing a circular motion.Major
@apaul34208 - Are you using Firefox ?Tumbledown
Try Chrome or IE, seems Firefox has issues! Tried figuring out what the problem in FF is, but it just won't do what I tell it.Tumbledown
I worked out the problem, I had transition: transform .2s; set on #canoe which was gumming up the works in FF. Unfortunately without it seems a bit twitchy. Do you know of any other way to smooth things out?Major
Increasing the timeouts a little would probably make it smoother but at the same time a little "slower", see if this helps any -> jsfiddle.net/unuCD/26Tumbledown
@Tumbledown if you're now using JQuery versions prior to ver 1.7.2, it will add the css prefixes for you, so you may just use: transform' : 'rotate('+degree+'deg)' and other browser specific lines will be handled automatically.Concessive

© 2022 - 2024 — McMap. All rights reserved.