jquery ui drag easing/inertia
Asked Answered
U

2

5

How do you enable easing or inertia when dragging an element using jquery ui draggable (http://jqueryui.com/demos/draggable/)? I'd like to recreate the easing similar to maps.google.com that as you throw/drag the map it eases into place. Ideally, I'd like to move the element move based on the force you throw/drag the element. How do you accomplish this functionality? Maybe jquery ui draggable isn't necessary, but I'm looking to emulate the dragging and easing found on Google Maps.

Thanks!

Utilize answered 14/12, 2010 at 19:51 Comment(1)
If you don't have to use jQuery UI, then this is one of the most elegant vanilla JS solutions I've seen: jsfiddle.net/soulwire/znj683b9Sinner
A
21

I used some ideas from here but integrated them with jQuery UI instead. You'll have to implement logic to handle a momentum animation that pushes the element out-of-bounds (outside it's parent container's boundaries)

The resulting code:

$(function() {
    var $d = $("#draggable");

    var x1, x2,
        y1, y2,
        t1, t2;  // Time

    var minDistance = 40; // Minimum px distance object must be dragged to enable momentum.

    var onMouseMove = function(e) {
        var mouseEvents = $d.data("mouseEvents");
        if (e.timeStamp - mouseEvents[mouseEvents.length-1].timeStamp > 40) {
            mouseEvents.push(e);
            if (mouseEvents.length > 2) {
                mouseEvents.shift();
            }
        }
    }

    var onMouseUp = function() {
        $(document).unbind("mousemove mouseup");
    }

    $d.draggable({
        start: function(e, ui) {
            $d.data("mouseEvents", [e]);
            $(document)
                .mousemove(onMouseMove)
                .mouseup(onMouseUp);
        },
        stop: function(e, ui) {
            $d.stop();
            $d.css("text-indent", 100);

            var lastE = $d.data("mouseEvents").shift();

            x1 = lastE.pageX;
            y1 = lastE.pageY;
            t1 = lastE.timeStamp;
            x2 = e.pageX;
            y2 = e.pageY;
            t2 = e.timeStamp;

            // Deltas
            var dX = x2 - x1,
                dY = y2 - y1,
                dMs = Math.max(t2 - t1, 1);

            // Speeds
            var speedX = Math.max(Math.min(dX/dMs, 1), -1),
                speedY = Math.max(Math.min(dY/dMs, 1), -1);

            // Distance moved (Euclidean distance)
            var distance = Math.sqrt(Math.pow(x1-x2, 2) + Math.pow(y1-y2, 2));

            if (distance > minDistance) {
                // Momentum
                var lastStepTime = new Date();
                $d.animate({ textIndent: 0 }, {
                    duration: Math.max(Math.abs(speedX), Math.abs(speedY)) * 2000,
                    step: function(currentStep){
                        speedX *= (currentStep / 100);
                        speedY *= (currentStep / 100);

                        var now = new Date();
                        var stepDuration = now.getTime() - lastStepTime.getTime();

                        lastStepTime = now;

                        var position = $d.position();

                        var newLeft = (position.left + (speedX * stepDuration / 4)),
                            newTop = (position.top + (speedY * stepDuration / 4));

                        $d.css({
                            left: newLeft+"px",
                            top: newTop+"px"
                        });
                    }
                });
            }
        }
    });
});

Try it out

Aristippus answered 14/12, 2010 at 21:31 Comment(5)
Fantastic! I made some tweaks and put it on jsFiddle: jsfiddle.net/entropo/gPdzCMerozoite
I was just looking at this code again and wondering why the $d.css("text-indent", 100); line is needed? I tried removing it, which caused the momentum portion to never trigger. I also tried adding text to the div, which revealed that the text-indent value slowly decreased to 0 after the momentum portion fires... which is bizarre.Merozoite
That line sets it up so that the script animates from 100% to 0% (property is irrelevant) every time you let the mouse go. We take advantage of this inside the animate method's step property to manually move the object "one step at a time", producing the drifting effect. I can't take full credit for this though. I took a lot of the logic from the article I linked.Aristippus
Doesn't work in FF 17.0.1 (using Jquery 1.9 + JQuery UI 1.9.2). The box gets negative top pretty much straightaway. In Chrome - the box stops responding on mouse moves after couple of successful moves.Disestablish
Needed this, thanks! Seems like we are going to a really nice place regarding gravity and physics with JavaScript. Also just confirmed it, and it's working on the latest Firefox Quantum release 65.0.1. Great stuff!Russophobe
R
9

The work simshaun did on this is fantastic.

I messed with his version and got a bit smoother animation with the jquery.easing plugin.
Try it out on jsfiddle.

$(document).ready(function() {
    $('#dragme').draggable({
        start: function(e, ui) {
            dragMomentum.start(this.id, e.clientX, e.clientY, e.timeStamp);
         },
        stop: function(e, ui) {
            dragMomentum.end(this.id, e.clientX, e.clientY, e.timeStamp);
        }  
     });
});

var dragMomentum = new function () {    
    var howMuch = 30;  // change this for greater or lesser momentum
    var minDrift = 6; // minimum drift after a drag move
    var easeType = 'easeOutBack';

    //  This easing type requires the plugin:  
    //  jquery.easing.1.3.js  http://gsgd.co.uk/sandbox/jquery/easing/

    var dXa =[0];
    var dYa =[0];
    var dTa =[0];

    this.start = function (elemId, Xa, Ya, Ta)  {
          dXa[elemId] = Xa;
        dYa[elemId] = Ya;
        dTa[elemId] = Ta;

      }; // END dragmomentum.start()

    this.end = function (elemId, Xb, Yb, Tb)  {        
        var Xa = dXa[elemId];
        var Ya = dYa[elemId];
        var Ta = dTa[elemId];
        var Xc = 0;
        var Yc = 0;

        var dDist = Math.sqrt(Math.pow(Xa-Xb, 2) + Math.pow(Ya-Yb, 2));
        var dTime = Tb - Ta;
        var dSpeed = dDist / dTime;
        dSpeed=Math.round(dSpeed*100)/100;

        var distX =  Math.abs(Xa - Xb);
        var distY =  Math.abs(Ya - Yb);

        var dVelX = (minDrift+(Math.round(distX*dSpeed*howMuch / (distX+distY))));
        var dVelY = (minDrift+(Math.round(distY*dSpeed*howMuch / (distX+distY))));

        var position = $('#'+elemId).position();
        var locX = position.left;
        var locY = position.top;

        if ( Xa > Xb ){  // we are moving left
            Xc = locX - dVelX;
        } else {  //  we are moving right
            Xc = locX + dVelX;
        }

        if ( Ya > Yb ){  // we are moving up
            Yc = (locY - dVelY);
        } else {  // we are moving down
            Yc = (locY + dVelY);
        }

        var newLocX = Xc + 'px';
        var newLocY = Yc + 'px';

        $('#'+elemId).animate({ left:newLocX, top:newLocY }, 700, easeType );

    }; // END  dragmomentum.end()

};  // END dragMomentum()
Ruralize answered 11/4, 2011 at 20:58 Comment(3)
I was just playing around with this. A little quirky (atleast in Chrome), but nice. For example, try changing the easing to easeOutQuad (although it probably does this for other easing methods) and try throwing the object out of the box. Sometimes it will ease outside of the specified bounding box.Aristippus
Yeah I noticed that, it's finicky with a containment box. Inside an "overflow:hidden" box, it performs pretty nicely for modern browsers... except not on an iPad. Which sucks, as that's a major target for what I am trying. I imagine I will roll my own bounding rules into the dragmoMentum.end function, and use that to ease back inside the box.Ruralize
Finally got a solution that plays nicely with containment boxes. See it here (jsfiddle).Ruralize

© 2022 - 2024 — McMap. All rights reserved.