jQuery Sortable with animation

Anderson Oliveira picture Anderson Oliveira · Feb 20, 2011 · Viewed 30k times · Source

I'm using jQuery and Sortable to arrange my list of items (and this http://dragsort.codeplex.com).

All works perfect.

I'm using a function on dragEnd to arrange the lists in order.

Here is my code:

$("#list1, #list2").dragsort({ dragSelector: "div",
                               dragBetween: true,
                               dragEnd: saveOrder,
                               placeHolderTemplate: "<li class='placeHolder'><div></div></li>" });

function saveOrder() {
    var data = $("#list1 li").map(function() { return $(this).children().html(); }).get();
    $("input[name=list1SortOrder]").val(data.join("|"));
};

My question: Is there anyway that I can do an animation while I'm dragging? Or reposition the elements while dragging? I just need it to work on Safari.

One example is this:

http://www.youtube.com/watch?v=U3j7mM_JBNw

Look at the drag/drop (0:30) and you'll see what I'm talking about.

Thanks.

Answer

Chris Kempen picture Chris Kempen · Nov 16, 2012

A bit late to the party, but I was determined to get a solution going with jQuery as there was very little help on this topic, especially replicating the functionality that exists on web apps like Facebook and their albums' images dragging and dropping to reorder, and the pretty animations that go along with that...

So I've come up with a solution that seems to work pretty great, and I'll do my best to explain it to the best of my abilities! Here goes...

The biggest problem here was to not only animate the sortables, but to figure out where they needed to animate to - fantastic when it comes to floating elements like images in a gallery! To get around this, I decided to .clone() the original floating LI items, position the clones absolutely under the original LI items using a z-index value that was less than the original LI items, and then when the change event fired from the jQuery sortable I could detect the position of the original LI and animate the absolutely positioned clones to those positions. The rest was to simply show / hide elements appropriately to get the desired effect.

Here's the code, starting with the HTML:

<ul id="original_items">
    <li><img src="something.jpg" /></li>
    <li><img src="something.jpg" /></li>
    <li><img src="something.jpg" /></li>
</ul>

<ul id="cloned_items">
</ul>

So we have the original items we're trying to sort, and a container for the cloned items. Time for the CSS:

#original_items, #cloned_items {
    list-style: none;
}

#original_items li {
    float: left;
    position: relative;
    z-index: 5;
}

#cloned_items li {
    position: absolute;
    z-index: 1;
}

With our CSS, we're just removing any list styling, floating our original elements, and setting up the z-index requirements to ensure the cloned items lie underneath the original items. Note the relative position on the original items to make sure they behave as expected. Why underneath you ask? It will (hopefully) become clear with some Javascript:

jQuery(function(){

    // loop through the original items...
    jQuery("#original_items li").each(function(){

        // clone the original items to make their
        // absolute-positioned counterparts...
        var item = jQuery(this);
        var item_clone = item.clone();
        // 'store' the clone for later use...
        item.data("clone", item_clone);

        // set the initial position of the clone
        var position = item.position();
        item_clone.css("left", position.left);
        item_clone.css("top", position.top);

        // append the clone...
        jQuery("#cloned_items").append(item_clone);
    });

    // create our sortable as usual...
    // with some event handler extras...
    jQuery("#original_items").sortable({

        // on sorting start, hide the original items...
        // only adjust the visibility, we still need
        // their float positions..!
        start: function(e, ui){
            // loop through the items, except the one we're
            // currently dragging, and hide it...
            ui.helper.addClass("exclude-me");
            jQuery("#original_items li:not(.exclude-me)")
                .css("visibility", "hidden");

            // get the clone that's under it and hide it...
            ui.helper.data("clone").hide();
        },

        stop: function(e, ui){
            // get the item we were just dragging, and
            // its clone, and adjust accordingly...
            jQuery("#original_items li.exclude-me").each(function(){
                var item = jQuery(this);
                var clone = item.data("clone");
                var position = item.position();

                // move the clone under the item we've just dropped...
                clone.css("left", position.left);
                clone.css("top", position.top);
                clone.show();

                // remove unnecessary class...
                item.removeClass("exclude-me");
            });

            // make sure all our original items are visible again...
            jQuery("#original_items li").css("visibility", "visible");
        },

        // here's where the magic happens...
        change: function(e, ui){
            // get all invisible items that are also not placeholders
            // and process them when ordering changes...
            jQuery("#original_items li:not(.exclude-me, .ui-sortable-placeholder)").each(function(){
                var item = jQuery(this);
                var clone = item.data("clone");

                // stop current clone animations...
                clone.stop(true, false);

                // get the invisible item, which has snapped to a new
                // location, get its position, and animate the visible
                // clone to it...
                var position = item.position();
                clone.animate({
                    left: position.left,
                    top:position.top}, 500);
            });
        }
    });
});

Wow, I really hope this makes sense and helps someone animate their sortable lists, but this is a working example for anyone who's interested! :)