0

Seems like every 50-100 jsfiddle updates lead me to the stackoverflow community to help solve a new issue I create! so Thanks!

Background: I have several functions containing animations, functions are to be fired after the previous one is completed. With some help and research I started using $.when and .promise() to control this stuff over my initial very ugly callback strings & setTimeouts on each function.

My dilemma is this: At some point the user may want to pause everything and resume it again at their leisure. I can't seem to add this functionality!

I'm able to pause all animations with $('div:animated').stop() but that is temporary as .promise() still resolves! Any way to override that behavior? Any restructuring that may help make a pause and resume easier to implement?

Tomorrow I plan on going down the path of adding all animations to a proxy objects queue but not sure how well creating a global queue on a new object will work with the existing structure. I also intent on looking at adding some boolean to check if value is off then don't call functions or animate otherwise do. I'm not sure if either of those solutions are the appropriate direction I should take?

after previewing this post I'd also be inclined to learn how I could refactor my nested start button function!

Any direction on implementing pause and resume functionality will be greatly appreciated.

The code below is much simplified and each function contains more elements & animations.

a fiddle here

JS:

    $('#start').click( function() {
    // run a and wait one second, when done run b and wait 1 second...
    a().delay(100).promise().done( function(){
        b().delay(100).promise().done( function(){
            c().delay(100).promise().done( function(){
                d().delay(100).promise().done( function(){
                    e().delay(100).promise().done( function(){
                        f().delay(100).promise().done( function(){
                            alert('all done!');
                        });
                    });
                });
            });
        });
    });


    });

    $('#reset').click( function() {
        $('.box').css({
            top: '0px'
        });
    });
    $('#pause').click( function() {
        $('div:animated').stop();
    });
   function a() {
        return $('.box1').animate({top: '100px'}, 1000, function(){
            $('.box1').animate({top: '50px'}, 1000);        
        });
        return $('.box6').animate({top: '200px'}, 4000);
    }
    function b(){
        return $('.box2').animate({top: '100px'}, 1000);
    }
    function c(){
        return $('.box3').animate({top: '100px'}, 1000, function(){
            $('.box3').animate({top: '50px'}, 1000);        
        });
    }
    function d(){
        return $('.box4').animate({top: '100px'}, 1000);
    }
    function e(){
        return $('.box5').animate({top: '100px'}, 1000, function(){
            $('.box5').animate({top: '50px'}, 1000);        
        });
    }
    function f(){
        return $('.box6').animate({top: '100px'}, 1000);
    }

​ html & CSS

<button id='start'>animate</button>
<button id='reset'>Reset</button>
<button id='pause'>pause</button>
<div class='boxes'>
    <div class='box box1'></div>
    <div class='box box2'></div>
    <div class='box box3'></div>
    <div class='box box4'></div>
    <div class='box box5'></div>
    <div class='box box6'></div>
</div>​
.boxes{position: relative}
.box{width: 40px; height: 40px; background-color: green; position:absolute;}
.box1{left: 0px;}
.box2{left: 60px;}
.box3{left: 120px;}
.box4{left: 180px;}
.box5{left: 240px;}
.box6{left: 300px;}
​

EDIT You're answers have provided the learning experience I was looking for; I really appreciate the mind share! My contrived example may not showcase the real world problem enough as the answers posed do not allow multiple animations to fire at the same time? I don't think at least? For example box1 and box2 may move at the same time, or when box3 fires so does box6. The initial VERY UGLY promise system I implemented used many functions as each function was composed of many animations. How would this be achieved with the proposed answers?

EDIT2 Two answers, charlietfl's lets me easily add secondary functions that fire simultaneously; very important. Alnitak's has an elegant way of constructing an array and applying it to the animations that is very easy to change animation types (fadeOut(), fadeIn()).

So what I'm attempting to do is combine the two such that I can create a nested array for secondary animations that uses Alnitaks array format:

['.box1', 'animate', { top:  '50px'}, 1000],

SO in short i'm still working on all answers but getting close / nearly there with charlietfls's. Adding Alnitak's array to his or adding charlietfl's nesting to Alnitak's.

This got more intense then I ever intended; I appreciate your contributions and code to learn from.... Very informative!

Jonas
  • 121,568
  • 97
  • 310
  • 388
twinturbotom
  • 1,504
  • 1
  • 21
  • 34

2 Answers2

3

The jQuery method of allowing multiple simultaneous animations is all very well, but when your animations are all sequential it's overkill.

I would suggest creating an array of animation descriptions, and then in a simple loop just invoke those one at a time.

Each description would need to know which element to animate, which function to call, and what the function arguments are.

Pausing the animation is then simply a question of not progressing through the loop:

function next() {
    if (!animating) return;

    var a = list.shift();
    if (a) {
        var el = a[0];
        var fn = a[1];
        var args = a.slice(2);
        $.fn[fn].apply($(el), args).promise().done(next);
    } else {
        alert('all done!');
    }
};

See http://jsfiddle.net/alnitak/ANRa3 for a demo.

Alnitak
  • 334,560
  • 70
  • 407
  • 495
  • this could definitely work especially since you can track how far you are down your animation script and continue where you left off. – cillierscharl Nov 16 '12 at 22:54
  • I started writing one - it's incomplete because I didn't add a "resume" feature yet. The main part is demonstrating how you can use `.apply` to call an arbitrary jQuery function on an element. http://jsfiddle.net/alnitak/ANRa3/ – Alnitak Nov 16 '12 at 23:00
  • fml. this is so clean, mate - if i could +1 this again i would; just something about `$.fn.apply` and self executing functions that makes life worth living. – cillierscharl Nov 16 '12 at 23:06
  • OK, I've cleaned this up somewhat now. It now resumes and resets happily. – Alnitak Nov 16 '12 at 23:29
  • Thanks for your contribution, I appreciate it! You've answered a question of mine or two before, your awesome! Could you please comment on an EDIT I made at the bottom of the question? I'm confused on how to fire multiple animations at once using your strategy (say box3 and box6) does this mean I would have to make an array of arrays to iterate through? – twinturbotom Nov 17 '12 at 13:10
1

Rather than using the callback of animate to call a secondary animation, if you chain animate().delay().animate() you can return the promise for the secondary animation.

My solution uses nested arrays of objects so you can easily add as many animations in one sequence as you wish. The trick is to create an array of all of the promises within the group and use that array to determine when the full group of animations is complete.

Sample data:

var animations = [
   /* first group array */
    [ {  "sel": ".box1","main": {"css": {"top": "100px"},"dur": 1000},
         "secondary": {"delay": 100,"css": {"top": "50px"},"dur": 1000    }
        },
        {"sel": ".box2","main": {"css": {"top": "100px"},"dur": 1000}},
       {"sel": ".box4","main": {"css": {"top": "100px"},"dur": 1000}},
      {    "sel": ".box5","main": {"css": {"top": "100px"},"dur": 1000},
            "secondary": {"delay": 100,"css": {"top": "50px"},"dur": 1000    }
        }      
    ],
     /* second group array */
    [ { "sel": ".box3","main": {"css": {"top": "100px"},"dur": 1000},
            "secondary": {"delay": 100,"css": {"top": "50px"},"dur": 1000    }
        },
       {"sel": ".box4","main": {"css": {"top": "100px"},"dur": 1000}}
    ]
];

JS:

var curr = -1;
var paused = false;

function animateBoxes() {
    if (paused) {
        return;
    }
    //curr = (curr + 1 < animations.length) ? curr + 1 : 0;
    curr=curr+1;

    if( !animations[curr]){
        alert('DONE!!');
        return;
    }
    /* array of animation deffereds*/
    var anims = [];

    $.each(animations[curr], function(i, currAnim) {
        var anim = boxAnimate(currAnim);
        anims.push(anim)
    });

    $.when.apply(null, anims).done(function() {
        animateBoxes();
    });
}


$('#start').click(function() {
    paused = false;
    animateBoxes();
});

$('#reset').click(function() {
    curr = -1;
    stopanimation();
    $('.box').css({
        top: '0px'
    });
});
$('#pause').click(function() {
    stopanimation();
});


function stopanimation() {
    paused = true;
    $('div:animated').stop(true, true);

}

function boxAnimate(obj) {

    var $el = $(obj.sel),
        anim;
    if (obj.secondary) {
        /* chain animate & delay to get promise for last effect */
        anim = $el.animate(obj.main.css, obj.main.dur)
                  .delay(obj.secondary.delay)
                  .animate(obj.secondary.css, obj.secondary.dur)
    } else {
        anim = $el.animate(obj.main.css, obj.main.dur)
    }
    return anim;
}

DEMO: http://jsfiddle.net/SWHTx/5/

charlietfl
  • 170,828
  • 13
  • 121
  • 150
  • Thanks for the help here! Would you mind commenting on the EDIT I made to the bottom of the post. I like the simplicity of your example, unfortunately the real world problem uses many animation types, some firing at once, some after others. Thanks – twinturbotom Nov 17 '12 at 13:12
  • OK.. you got my curiosity up. I completely revised my answer with a lot more robust method that allows you to add as many animations in a group as you want. Chaining animations rather than using callbacks makes it easier to get a promise from the last effect – charlietfl Nov 17 '12 at 16:58
  • WOW! Your good! I need to wrap my brain around the chain / secondary promise bit (as well as a few other things). I have fadeIn() and fadeOut() animations to integrate in this that I'm either going to try to add with if / then or maybe just animate css opacity. What direction do you recommend? I really appreciate this. The real world animation is pretty large, I'm going to start tackling it with your strategy over the next few days and get back to you on a solution checkmark. Thanks – twinturbotom Nov 17 '12 at 21:14
  • keep in mind that `fadeIn`/`fadeOut` are also animations and you can use animate as well. There are numerous ways to extend the object and code also like adding callbacks to the animations that may not be time critical to starting the next group. The more you can build into the object in standardized fashion the easier the coding is. It's hard to help with solutions without knowing more about the various types of effects, and how many might be chained together – charlietfl Nov 17 '12 at 21:21