JQuery draggable with ease

dandoen picture dandoen · Jun 18, 2011 · Viewed 19.7k times · Source

I would like to achieve the ease effect with Jquery's draggable. But I did not find the option in this plugin. So I was wondering if there are other plugins that have this option - or an easy solution.

The effect I am trying to achieve is something this: http://www.fileden.com/files/2009/6/4/2466215/dragease.swf

As you can see, upon dragging, the image movement is feeling much smoother because of the easing. I would also like to constraint the dragging to one axis, also need to make it revert back to its place. JQuery's draggable does have this last two options, so thats nice.

The example code already provides me with what I want (except the easing): http://jsfiddle.net/dandoen/NJwER/1/

Any advise would be appreciated.

Cheers, Dandoen

Answer

DarthJDG picture DarthJDG · Jun 20, 2011

You can use the original draggable, but you will need a few lines of extra code. We create an invisible helper and manually animate the original object to follow it with custom easing. You can play with the animation duration and the easing function to customise the effect.

In case you would use any draggables, they should be properly activating when the mouse hovers over them without having to wait for the object to get there.

The only drawback is that as we're changing the original element's position manually, the revert parameter can't be used, but it can be easily sorted out by saving the starting position and animating the object back when dragging stops.

HTML:

<div id="draggable" class="ui-widget-content">
    <p>Revert the original</p>
</div>

CSS:

#draggable {
    position: relative;
    width: 100px;
    height: 100px;
    padding: 0.5em;
    float: left;
    margin: 0 10px 10px 0;
    background-color: red;
    border: 2px solid gray;
}

Javascript:

$(function() {
    $("#draggable").draggable({
        // Can't use revert, as we animate the original object
        //revert: true,

        axis: "y",
        helper: function(){
            // Create an invisible div as the helper. It will move and
            // follow the cursor as usual.
            return $('<div></div>').css('opacity',0);
        },
        create: function(){
            // When the draggable is created, save its starting
            // position into a data attribute, so we know where we
            // need to revert to.
            var $this = $(this);
            $this.data('starttop',$this.position().top);
        },
        stop: function(){
            // When dragging stops, revert the draggable to its
            // original starting position.
            var $this = $(this);
            $this.stop().animate({
                top: $this.data('starttop')
            },1000,'easeOutCirc');
        },
        drag: function(event, ui){
            // During dragging, animate the original object to
            // follow the invisible helper with custom easing.
            $(this).stop().animate({
                top: ui.helper.position().top
            },1000,'easeOutCirc');
        }
    });
});

Demo: http://jsfiddle.net/NJwER/4/

Update: Constrained-axis draggable

As requested, here's the modified code from this thread. The original is brianpeiris' brilliant axis-constrained draggables extension.

Changing it was very simple, just added the above bits to the code and made reverting optional. I renamed it to draggableXYE (E for easing that is). It might not be the most elegant solution, it would probably be easy to write it as a small extension to the draggableXY, but it will do the job.

Dragging feels quite interesting when you switch dynamic mode on, it eases movement when the draggable snaps from one axis to the other.

Javascript:

$.fn.draggableXYE = function(options) {
    var defaultOptions = {
        distance: 5,
        dynamic: false
    };
    options = $.extend(defaultOptions, options);

    // ADDED: Store startPosition for reverting
    var startPosition = this.position();

    // ADDED: Function to apply easing to passed element
    function AnimateElement(element, newpos) {
        $(element).stop().animate({
            top: newpos.top,
            left: newpos.left
        }, 1000, 'easeOutCirc');
    }

    this.draggable({
        distance: options.distance,
        // ADDED: Helper function to create invisible helper
        helper: function(){
            return $('<div></div>').css('opacity',0);
        },
        start: function(event, ui) {
            ui.helper.data('draggableXY.originalPosition', ui.position || {
                top: 0,
                left: 0
            });
            ui.helper.data('draggableXY.newDrag', true);
        },
        drag: function(event, ui) {
            var originalPosition = ui.helper.data('draggableXY.originalPosition');
            var deltaX = Math.abs(originalPosition.left - ui.position.left);
            var deltaY = Math.abs(originalPosition.top - ui.position.top);

            var newDrag = options.dynamic || ui.helper.data('draggableXY.newDrag');
            ui.helper.data('draggableXY.newDrag', false);

            var xMax = newDrag ? Math.max(deltaX, deltaY) === deltaX : ui.helper.data('draggableXY.xMax');
            ui.helper.data('draggableXY.xMax', xMax);

            var newPosition = ui.position;
            if (xMax) {
                newPosition.top = originalPosition.top;
            }
            if (!xMax) {
                newPosition.left = originalPosition.left;
            }

            // ADDED: Animate original object with easing to new position
            AnimateElement(this, newPosition);

            return newPosition;
        },
        // ADDED: Stop event to support reverting
        stop: function() {
            if (options.revert) {
                AnimateElement(this, startPosition);
            }
        }
    });
};

Usage:

$('.drag').draggableXYE({
    revert: true,
    dynamic: true
});

DEMO: http://jsfiddle.net/4C9p2/