Animated <ul> modification

slide <li> to their new position, fade new elements in, removed elements out.

by rodneyrehm

HTML

<ul id="list">
    <li id="L1">one</li>
    <li id="L2">two</li>
    <li id="L3">three</li>
    <li id="L4">four</li>
    <li id="L5">five</li>
    <li id="L6">six</li>
</ul>

<div id="newlist" style="display:none"><!--
<ul>
    <li id="L0">zero</li>
    <li id="L1">one</li>
    <li id="L3">three</li>
    <li id="L4">four</li>
    <li id="L7">alpha</li>
    <li id="L8">bravo</li>
    <li id="L5">five</li>
    <li id="L9">charlie</li>
    <li id="L6">six</li>
</ul>
--></div>

<button id="switch">modify</button>

CSS

#list {
    position: relative;
    font-size: 0;
}
#list > li {
    font-size: 16px;
    display: inline-block;
    width: 150px;
    height: 150px;
    margin: 5px;
    background: #EEE;
    border: 1px solid #CCC;
}
#list.transitioning > li {
    position: absolute;
}

JavaScript

function remove() {
    $(this).remove();
}

$('#switch').on('click', function() {
    var origin = {},
        destination = {},
        $list = $('#list'),
        $first = $list.children().first(),
        margin = {},
        $oldChildren = $list.children(),
        $newChildren = $($.trim(document.getElementById('newlist').firstChild.nodeValue)).children();
    
    // identify margins (to correct absolute positioning)
    $.each(["top", "left"], function(k, v) {
        var key = "margin-" + v;
        margin[v] = parseInt($first.css(key) || "0", 10);
        if (isNaN(margin[v])) {
            margin[v] = 0;
        }
    });
    
    // capture origin position
    $list.children().each(function() {
        var $this = $(this);
        origin[this.id] = $this.offset();
        //origin[this.id].top += margin.top;
        //origin[this.id].left += margin.left;
    });
    
    // switch in new elements
    $oldChildren.detach();
    $list.append($newChildren);
    
    // force reflow
    $list.css('top');
    
    // capture destination position
    $newChildren.each(function() {
        var $this = $(this);
        destination[this.id] = $this.offset();
        //destination[this.id].top -= margin.top;
        //destination[this.id].left -= margin.left;
    });
    
    // make stuff movable
    $list.addClass('transitioning');

    $newChildren.each(function(){
        $(this).offset(destination[this.id]);
    });
 
    // find elements to remove
    $oldChildren.each(function() {
        if (!destination[this.id]) {
            // insert element
            var $this = $(this);
            origin[this.id].top -= margin.top;
            origin[this.id].left -= margin.left;
            $this
                .offset(origin[this.id])
                .appendTo($list)
                .fadeOut(100, remove);
        }
    });

    // find elements to insert
    $newChildren.each(function() {
        var $this = $(this);
        if (!origin[this.id]) {
         ...