jQuery Sortable with animation
Asked Answered
I

6

19

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.

Ideology answered 20/2, 2011 at 21:57 Comment(4)
What does your css look like? This should be authentic behaviour actually..Recommendatory
My css is pretty simple. Just using float width and height.Ideology
You may be able to find a solution through adapting QuickSand - razorjack.net/quicksandBanville
I've raised a related feature request hereEyeopener
H
27

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! :)

Huckster answered 16/11, 2012 at 12:24 Comment(7)
@Daniel - Thank you for the feedback, I hope it made sense!Huckster
Thanks for this code it works really well. Just wondering if you know why this breaks when you use a % width on the li and then resize your browser?Knap
@Parallel2ne - It's difficult to say without having a look at some code, or knowing what exactly is breaking, but if I had to guess I'd say it's the cloned items that you're referring to as 'broken'? This might be due to the styling of the cloned items, as they are being placed absolutely and would require their container (ul#cloned_items) to be styled the same as the original items' container (ul#original_items) to properly accept appropriate widths as %'s. If the cloned items' container is not 100% wide, then there's nothing for the cloned items to base their width on. Make sense? :')Huckster
I do sooort of understand what you are saying :) it does fix itself after you perform another sortable action jsfiddle.net/jamestoone/dNfsJ/48 any help would be much appreciatedKnap
@Parallel2ne - This might be a silly question, but why were you floating the middle line of li elements (in gold) to the right instead of the left? I removed line 19 of the CSS (updated jsFiddle here), and the response seems to be better. I'm not sure if this fixes your original concerns?Huckster
CHeers for the help but it doesnt fix my original problem. I am floating the middle line and every other line to reverse the order so that it goes in a snakes and ladderes type of layout. I am experimenting doing that by using jquery to reverse th order of the li's after a certain number as it does seem to effect the draggable in a negative way.Knap
@Parallel2ne - Haaha maybe next time I should start by asking what the problem actually is! :) I based my original solution on floating elements to the left, and floating them right does seem to have a negative effect. I fear this may be beyond the scope of the OP and this answer, but perhaps re-ordering them using jQuery rather while preserving their left float CSS attributes would work quite nicely...good luck, and sorry that I couldn't be of more assistance! :)Huckster
S
18

Just impleneted what Chris Kempen said: http://jsfiddle.net/dNfsJ/

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);
        });
    }
});
Snuggery answered 16/3, 2013 at 9:54 Comment(0)
L
3

While this solution works great to create an initial transition, when the item snaps back, there is no transition. The solution is easier than I ever expected. All you need to do is adjust the revert option in .sortable()

Like this:

     <script>
      $(document).ready(function() {
        $( "#sortable" ).sortable({
         tolerance: 'pointer',
         revert: 'invalid'
        }).disableSelection();
      });
     </script>

jQuery UI API: http://api.jqueryui.com/sortable/#option-revert

This makes a nice and smooth transition to the item's new home.

Click here for EXAMPLE on jsFiddle

Leastways answered 30/1, 2014 at 18:6 Comment(1)
Unfortunately, the "revert" option does not apply when you "cancel" a sort or drop event. The canceled item snaps back instantly instead of smoothly animating.Ruisdael
N
2

Why did not you used Sortable on jqueryui? http://jsfiddle.net/KgNCD/

JS:

$( "#sortable" ).sortable({       
    start: function(e, ui){
        $(ui.placeholder).hide(300);
    },
    change: function (e,ui){
        $(ui.placeholder).hide().show(300);
    }
});                           
$("#sortable").disableSelection();

HTML:

<ul id="sortable">
    <li class="ui-state-default">1</li>
    <li class="ui-state-default">2</li>
    <li class="ui-state-default">3</li>
    <li class="ui-state-default">4</li>
    <li class="ui-state-default">5</li>
    <li class="ui-state-default">6</li>
    <li class="ui-state-default">7</li>
    <li class="ui-state-default">8</li>
    <li class="ui-state-default">9</li>
    <li class="ui-state-default">10</li>
    <li class="ui-state-default">11</li>
    <li class="ui-state-default">12</li>
</ul>
Nagano answered 22/2, 2011 at 13:34 Comment(1)
Yeah, I thought, but how can I animate the elements before I drop?Ideology
U
2

For anyone looking for a quick and easy solution to this, here is a plugin that allows you to quickly add sortable animations by specifying the animation length in the sortable parameters:

$('#sortable').sortable({
 animation: 200,
});

Demo: https://egorshar.github.io/jquery-ui-sortable-animation/

Github: https://github.com/egorshar/jquery-ui-sortable-animation

Unlash answered 15/11, 2021 at 9:28 Comment(0)
I
1

From the jsfiddle answer above (http://jsfiddle.net/KgNCD/2/):

$( "#sortable" ).sortable({       
    start: function(e, ui){
        $(ui.placeholder).hide(300);
    },
    change: function (e,ui){
        $(ui.placeholder).hide().show(300);
    }
});
Integumentary answered 15/2, 2012 at 17:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.