//OUTPUT: Animation callbacks

<New code>

//OUTPUT:

//sample animation code with function callbacks
$( '#imageId' ).animate( 
    {   
        left:   '-=50px',
        top:    '+=50px'    
    }, 
    {   
        duration:   1000, 
        easing:     "linear",
        start:  function () {
            // this happens BEFORE the animation
            document.querySelector( '#imageId' ).src 
                = 'http://drapak.ca/cpg/img/link-left.png';
        },
        complete: function () {
            // this happens AFTER the animation
            alert ("Animation finished!");
        }
    } 
);

Code that happens before and after an animation is complete

You have noticed that jQuery animations "stack up" outside of the normal flow of code. For example, you have noticed that your Javascript program may actually finish running, and that the jQuery animations will keep running. That is because jQuery animations are stacked in an animation queue.

This means that whenever an animation is called on an object, jQuery will automatically stack that animation on top of any others that were assigned to that object. It will complete each of the animations in order until the queue is empty.

This is good because you do not have to manually time things as much. And your Javascript can keep working, doing things in the background while waiting for animations to finish. This is handy if you want to say, move more than one thing at the same time using code, like a monster and a hero.

But it does cause a problem if you want to synchronize your Javascript with your animations.

Common case: sprite directions

One common irritation you have likely discovered is that sometimes your characters will change their image's direction before they actually move in that direction.

Let's look at what is going on. For the heck of it, let's do all the image source changes before moving anything. The result looks like this:

//OUTPUT: do all the source changes together, then all the animations together
var doAllSourceChangesThenAnimations = function () {
    // make all the image source changes
    document.querySelector( '#imageId' ).src 
        = 'http://drapak.ca/cpg/img/link-down.png';
    document.querySelector( '#imageId' ).src 
        = 'http://drapak.ca/cpg/img/link-right.png';
    document.querySelector( '#imageId' ).src 
        = 'http://drapak.ca/cpg/img/link-up.png';
    document.querySelector( '#imageId' ).src 
        = 'http://drapak.ca/cpg/img/link-left.png';

    // animate all the movement
    $( '#imageId' ).animate( { top: '+=50px' } );       
    $( '#imageId' ).animate( { left: '+=50px' } );
    $( '#imageId' ).animate( { top: '-=50px' } );
    $( '#imageId' ).animate( { left: '-=50px' } );
}


Then, let's alternate the code so that for each direction, it changes the source image, and then animates.

//OUTPUT: Do the sources change and animation for each step, one at a time
var changeSourceThenAnimateOneByOne = function () {
    // change image to face down and then animate down
    document.querySelector( '#imageId' ).src 
        = 'http://drapak.ca/cpg/img/link-down.png';
        
    $('#imageId').animate( { top: '+=50px' } );     
    
    // change image to face right and then animate right
    document.querySelector( '#imageId' ).src 
        = 'http://drapak.ca/cpg/img/link-right.png';
        
    $('#imageId').animate( { left: '+=50px' } );
    
    // change image to face up and then animate up
    document.querySelector( '#imageId' ).src 
        = 'http://drapak.ca/cpg/img/link-up.png';
        
    $( '#imageId' ).animate( { top: '-=50px' } );
    
    // change image to face left and then animate left
    document.querySelector( '#imageId' ).src 
        = 'http://drapak.ca/cpg/img/link-left.png';
        
    $( '#imageId' ).animate( { left: '-=50px' } );
    
    //NOTE: because of animation queues, this will change the image sources four
    //      times and THEN animate #imageId
};


Notice that it appears to do the exact same thing! This is because once Javascript processes an animation command, it sends the movement to the jQuery animation queue and then processes the next line of code.

By the end of the function, Javascript has changed all the image source links so fast you can't see it, but it has hardly begun the first movement of the animation queue.

If we could somehow squeeze the source image change into the animation queue everything will sync up. Luckily, jQuery allows you to assign a function that will be triggered at the beginning and end of an animation.

//OUTPUT:   Animate the movement, adding the image source change to the
//          start: property as an anonymous function.
//          Anything put inside start: will happen before the animation begins
var animateSourceChangesBeforeEachAnimation = function () {
    // change image to face down and then animate down
    $( '#imageId' ).animate( 
        { 
            top:        '+=50px' 
        }, 
        { 
            start:  function () {
                document.querySelector( '#imageId' ).src 
                    = 'http://drapak.ca/cpg/img/link-down.png';
            }
        } 
    );      
    
    // change image to face right and then animate right
    $( '#imageId' ).animate( 
        { 
            left:   '+=50px' 
        }, 
        { 
            start:  function () {
                document.querySelector( '#imageId' ).src 
                    = 'http://drapak.ca/cpg/img/link-right.png';
            }
        }
    );
    
    // change image to face up and then animate up
    $( '#imageId' ).animate( 
        { 
            top:        '-=50px' 
        }, { 
            start:  function () {
                    document.querySelector( '#imageId' ).src 
                        = 'http://drapak.ca/cpg/img/link-up.png';
            } 
        }
    );
    
    // change image to face left and then animate left
    $( '#imageId' ).animate( 
        { 
            left:   '-=50px' 
        }, 
        { 
            start:  function () {
                document.querySelector( '#imageId' ).src 
                    = 'http://drapak.ca/cpg/img/link-left.png';
            },
            complete: function () {
                alert( "All done!" );
            }
        } 
    );
};


Code template for animating source image changes

//sample animation code with function callbacks
$( '#imageId' ).animate( 
    {   
        left:   '-=50px',
        top:    '+=50px'    
    }, 
    {   
        duration:   1000, 
        easing:     "linear",
        start:  function () {
            // this happens BEFORE the animation
            document.querySelector( '#imageId' ).src 
                = 'http://drapak.ca/cpg/img/link-left.png';
        },
        complete: function () {
            // this happens AFTER the animation
            alert ("Animation finished!");
        }
    } 
);

Common case: triggering a function to decide where to animate next

Let's say that you are creating something that is supposed to wander around and seek something out, animating it's movement as it goes. Like a monster going after a hero.

That means that you will have to trigger your Javascript seeking code at the end of each animation cycle. You could make the animation duration equal to duration of a setInterval command, and trigger the seeking at equal time intervals, like a heartbeat.

But in my experience, the processing time of the animation and the setInterval command always get slightly off, causing strange shudders or delays. However, you can trigger your Javascript seeking code right as soon as an animation is complete.

This causes a kind of loop called an animation callback: some code creates an animation that finishes by calling that same code again. It is used quite often for smoother animation.

//common use: a sprite looks for new place to move after animation ends
//solves problem of setTimeout getting out of sync

$( '#animateId' ).animate(
    {   margin:     '0px' },    //animate some unimportant style, like margin width
    {   duration:   2000, 
        easing:     'linear',
        complete:   function () {
                        this.chooseDirectionToMove();
                    } 
    } 
);