22

In many cases I wish animation to be executed synchronously. Especially when I wish to make a a series of sequential animations.

Is there an easy way to make a jQuery animate function call synchronous?

The only way I thought about is to set a flag true when the animation has finished and to wait for this flag.

Elazar Leibovich
  • 32,750
  • 33
  • 122
  • 169

7 Answers7

29

jQuery cannot make synchronous animations.

Remember that JavaScript runs on the browser's UI thread.

If you make a synchronous animation, the browser will freeze until the animation finishes.

Why do you need to do this?

You should probably use jQuery's callback parameter and continue your method code in the callback, like this:

function doSomething() {
    var thingy = whatever;
    //Do things
    $('something').animate({ width: 70 }, function() {
        //jQuery will call this method after the animation finishes.
        //You can continue your code here.
        //You can even access variables from the outer function
        thingy = thingy.fiddle;
    });
}

This is called a closure.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • I think you can escape from UI thread with setTimeout, so that I can definitely use nonblocking animation in a setTimeout function to have a sane looking code for sequential animations. – Elazar Leibovich Oct 20 '09 at 20:11
  • 8
    You are wrong. `setTimeout` does not execute the callback on a different thread; it waits for the UI thread to become free and then invokes the callback on the UI thread. Therefore, Javascript developers don't need to deal with all of the intricacies of thread-safe development. – SLaks Oct 20 '09 at 20:38
  • test my answer solution please – cusspvz Aug 25 '10 at 16:19
7

I think you should take a look at the jQuery queue() method.

Not only does queue()'s doc explain jQuery animations don't really block the UI, and actually queues them after one another.

It also provides with a way to make your animations and function calls sequential (this is my best understanding of what you mean by "synchronous"), like:

$("#myThrobber")
    .show("slow")                 // provide user feedback 
    .queue( myNotAnimatedMethod ) // do some heavy duty processing
    .hide("slow");                // provide user feedback (job's 

myNotAnimatedMethod() { // or animated, just whatever you want anyhow...
    // do stuff
    // ...

    // tells #myThrobber's ("this") queue your method "returns", 
    // and the next method in the queue (the "hide" animation) can be processed
    $(this).dequeue();

    // do more stuff here that needs not be sequentially done *before* hide()
    // 
}  

This is of course overkill with asynchronous processing; but if your method is actually a plain old synchronous javascript method, that could be the way to do it.

Hope this helps, and sorry for my poor english...

Alain BECKER
  • 787
  • 5
  • 15
  • synchronous means `fwrite(big_data)` returns AFTER `fwrite` finished writing. Asynchronous means `fwrite` would return immediately, and writing the big data is done in parallel/some other time. – Elazar Leibovich Apr 27 '10 at 07:00
  • Then I think I got it right: if you whish you animation of $("#myThrobber") for instance to be executed synchronously after fwrite(big_data), you can do so with the following two statements: (1) $("#myThrobber").queue(fwrite(big_data)).animate(//whatever); AND (2) end you fwrite() method with $("#myThrobber").dequeue(); NB: my sample was wrong on calling $(this).dequeue(); it should read: $("#myThrobber").dequeue(); Forgot that as I created that sample from code where I needed to .queue( jQuery.proxy(myNotAnimatedMethod,this) ) Sorry for that confusion. – Alain BECKER Apr 27 '10 at 11:34
2

jQuery provides a "step" callback for its .animate() method. You can hook into this to do synchronous animations:

jQuery('#blat').animate({
  // CSS to change
  height: '0px'
},
{
  duration: 2000,
  step: function _stepCallback(now,opts) {
    // Stop browser rounding errors for bounding DOM values (width, height, margin, etc.)
    now = opts.now = Math.round(now);

    // Manipulate the width/height of other elements as 'blat' is animated
    jQuery('#foo').css({height: now+'px'});
    jQuery('#bar').css({width: now+'px'});
  },
  complete: function _completeCallback() {
    // Do some other animations when finished...
  }
}
shuckster
  • 5,279
  • 2
  • 23
  • 22
  • 2
    The step callback has nothing to do with the question. The complete callback is exactly what the other answers are saying. – SLaks Dec 08 '09 at 18:06
  • He is trying to execute one animation after another one finishes. He is not trying to animate two elements at once. – SLaks Dec 08 '09 at 18:08
  • My apologies -- my brain inserted an "a" where there wasn't one! – shuckster Dec 09 '09 at 00:18
  • 1
    Might not have been what the OP was asking, but it was what I needed - thanks! – Hobo Jan 22 '15 at 20:37
1

I agree with @SLaks on this one. You should be using jQuery's callbacks for given animations to create your synchronous animation. You can essentially take whatever you have for your current animation and split it up like so:

$yourClass = $('.yourClass');
$yourClass.animate({
    width: "70%"
}, 'slow', null, function() {
    $yourClass.animate({
        opacity: 0.4
    }, 'slow', null, function() {
        $yourClass.animate({
            borderWidth: "10px"
        });
    });
});
Corey Ballou
  • 42,389
  • 8
  • 62
  • 75
  • You probably understand how it would look when it scales to 20 actions... also see my response to @SLaks. – Elazar Leibovich Oct 20 '09 at 20:10
  • If you don't like the way it looks, try not indenting the callbacks (or only indenting them a little bit). This is the only way to do it. – SLaks Oct 20 '09 at 21:38
0

I came across this http://lab.gracecode.com/motion/ Really easy to use and works great in combination with jquery.

EDIT The links seems dead. If I've followed the trail those the wayback archive correctly, the code is at https://github.com/feelinglucky/motion

Hobo
  • 7,536
  • 5
  • 40
  • 50
0

jQuery can make synchronous animations. Check this out:

function DoAnimations(){
  $(function(){
    $("#myDiv").stop().animate({ width: 70 }, 500);
    $("#myDiv2").stop().animate({ width: 100 }, 500);
  });
}
Mike
  • 19,267
  • 11
  • 56
  • 72
Ben
  • 1
0

Here is a module i put together a while back to assist in running animations sequentially.

Usage:

var seq = [
    { id: '#someelement', property:'opacity', initial: '0.0', value:'1.0', duration:500 },
    { id: '#somethingelse', property:'opacity', value:'1.0', duration: 500 }
];

Sequencer.runSequence(seq);

var Sequencer = (function($) {
    var _api = {},
        _seq = {},
        _seqCount = 0,
        _seqCallback = {};

    function doAnimation(count, step) {
        var data = _seq[count][step],
            props = {};

            props[data.property] = data.value

        $(data.id).animate(props, data.duration, function() {
            if (step+1 < _seq[count].length) {
                doAnimation(count, ++step);
            } else {
                if (typeof _seqCallback[count] === "function") {
                    _seqCallback[count]();
                }
            }
        });
    }

    _api.buildSequence = function(id, property, initial, steps) {
        var newSeq = [],
            step = {
                id: id,
                property: property,
                initial: initial
            };

        $.each(steps, function(idx, s) {
            step = {};
            if (idx == 0) {
                step.initial = initial;
            }
            step.id = id;
            step.property = property;
            step.value = s.value;
            step.duration = s.duration;
            newSeq.push(step);
        });

        return newSeq;
    }

    _api.initSequence = function (seq) {
        $.each(seq, function(idx, s) {              
            if (s.initial !== undefined) {
                var prop = {};
                prop[s.property] = s.initial;
                $(s.id).css(prop);
            }            
        });
    }

    _api.initSequences = function () {
        $.each(arguments, function(i, seq) {
            _api.initSequence(seq);
        });
    }

    _api.runSequence = function (seq, callback) {
        //if (typeof seq === "function") return;
        _seq[_seqCount] = [];
        _seqCallback[_seqCount] = callback;

        $.each(seq, function(idx, s) {

            _seq[_seqCount].push(s);
            if (s.initial !== undefined) {
                var prop = {};
                prop[s.property] = s.initial;
                $(s.id).css(prop);
            }

        });


        doAnimation(_seqCount, 0);
        _seqCount += 1;
    }

    _api.runSequences = function() {
        var i = 0.
            args = arguments,
            runNext = function() {
                if (i+1 < args.length) {
                    i++;
                    if (typeof args[i] === "function") {
                        args[i]();
                        runNext();
                    } else {
                        _api.runSequence(args[i], function() {
                            runNext();
                        });
                    }
                }
            };

        // first we need to set the initial values of all sequences that specify them
        $.each(arguments, function(idx, seq) {
            if (typeof seq !== "function") {
                $.each(seq, function(idx2, seq2) {
                    if (seq2.initial !== undefined) {
                        var prop = {};
                        prop[seq2.property] = seq2.initial;
                        $(seq2.id).css(prop);
                    }
                });
            }

        });

        _api.runSequence(arguments[i], function (){
            runNext();
        });

    }

    return _api;
}(jQuery));
Jason Miesionczek
  • 14,268
  • 17
  • 76
  • 108