See snapshots of what Amedia is doing in real-time.

Join now and start following @amedia_creative.

Get the Kontain app and take your own snapshots!

View all of amedia_creative's updates Kontained: 11 months ago

How We Chained Animation In jQuery

Displaying: 1 of 1

 By Hisa Ishibashi

Lead Backend Developer at Amedia Creative

 

While working on a project a few weeks ago, a client of ours placed an exciting new challenge on our laps. This challenge involved a few requirements, which needed to be met and animated via JavaScript. At Amedia Creative, we use jQuery as our preferred JavaScript development framework for our web design and development services. Even though jQuery made things easier, this piece of animation proved to be a small challenge.

 

Solution = Team+Effort+Skills+Experience+Knowledge... coffee?

 

There were four requirements for this interactive piece.

 

1: The client submitted a design that had an image spanning across multiple squares. This design created a "loft" window like affect. 

 

Loft Windows


 

2: Each image needed to rotate. The total images in the sequence were three. These images needed to appear in a consistent order--not appear at random.

3: Each square needed to animate the new image in separately at random. What does this mean? If we have a total of twelve squares, each square would need be replaced with a piece of the new image. This needed to occur at different times, additionally, in a ramdom order.

 

4: These images needed to be able to be managed via a CMS.

 

Last Thursday, one of my co-workers & Lead Frontend Developer here at Amedia Creative, Fito, was working on this issue. Fito did a fantastic job on the design. He created the required animation with jQuery. This was the code he provided in his proof of concept.

 

    /*---------- Landing Animation ----------*/

    var curni = 1;

    var ni = 2;

    window.setTimeout('xoxo()', 1600);

    xoxo=function(){

        $('.anim_blk6').animate({opacity:'0'},400, function(){

            $('.anim_blk6').removeClass('back0'+curni).addClass('back0'+ni).animate({opacity:'1'},600);

            $('.anim_blk4').animate({opacity:'0'},400, function(){

                $('.anim_blk4').removeClass('back0'+curni).addClass('back0'+ni).animate({opacity:'1'},600);

                $('.anim_blk11').animate({opacity:'0'},400, function(){

                    $('.anim_blk11').removeClass('back0'+curni).addClass('back0'+ni).animate({opacity:'1'},600);

                    $('.anim_blk5').animate({opacity:'0'},400, function(){

                        $('.anim_blk5').removeClass('back0'+curni).addClass('back0'+ni).animate({opacity:'1'},600);

                        $('.anim_blk1').animate({opacity:'0'},400, function(){

                            $('.anim_blk1').removeClass('back0'+curni).addClass('back0'+ni).animate({opacity:'1'},600);

                            $('.anim_blk8').animate({opacity:'0'},400, function(){

                                $('.anim_blk8').removeClass('back0'+curni).addClass('back0'+ni).animate({opacity:'1'},600);

                                $('.anim_blk9').animate({opacity:'0'},400, function(){

                                    $('.anim_blk9').removeClass('back0'+curni).addClass('back0'+ni).animate({opacity:'1'},600);

                                    $('.anim_blk3').animate({opacity:'0'},400, function(){

                                        $('.anim_blk3').removeClass('back0'+curni).addClass('back0'+ni).animate({opacity:'1'},600);

                                        $('.anim_blk7').animate({opacity:'0'},400, function(){

                                            $('.anim_blk7').removeClass('back0'+curni).addClass('back0'+ni).animate({opacity:'1'},600);

                                            $('.anim_blk10').animate({opacity:'0'},400, function(){

                                                $('.anim_blk10').removeClass('back0'+curni).addClass('back0'+ni).animate({opacity:'1'},600);

                                                $('.anim_blk2').animate({opacity:'0'},400, function(){

                                                    $('.anim_blk2').removeClass('back0'+curni).addClass('back0'+ni).animate({opacity:'1'},600);

                                                    $('.anim_blk12').animate({opacity:'0'},400, function(){

                                                        $('.anim_blk12').removeClass('back0'+curni).addClass('back0'+ni).animate({opacity:'1'},600, function(){

                                                            curni++;

                                                            ni++;

                                                            if(curni > 4){ curni = 1 };

                                                            if(ni > 4){ ni = 1 };

                                                            window.setTimeout('xoxo()', 5000);

                                                        });

                                                    });

                                                });

                                            });

                                        });

                                    });

                                });

                            });

                        });

                    });

                });

            });

        });

    };

    //end here

 

 

At first, I thought that the recursion was unnecessary. I felt, this type of development was a maintenance nightmare. However, the client's business requirements were fulfilled. That's a win in itself. Fito managed to create the look of a random sequence that was not actually random.

 

Later, Fito asked me how he could dynamically create a random sequence and keep track of blocks already changed. He explored using JavaScript's Math.random() coupled with a series of arrays.

 

The next day, Fito revised his proof of concept using Math.random(). At that time, the only missing piece was to keep track of random numbers using arrays. I was thinking, "how hard can it be?" Famous last words, because after an hour I could not figure out what was wrong. At that time I took a copy of Fito's code to my computer to address it further. I couldn't continue on it because I had to go back to the issue I was currently working on. After a day's worth of work and a night worth of fun I came back to the office at 1am and decided to put in some extra time to figure this out.

 

Remember when we thought the recursion was unnecessary? Well, I was wrong. Some of the assumptions I made were:

 

  1. I thought I could achieve the animation wrapping repeating the code inside a while(true) loop.
  2. I thought I could run the animation sequentially or in a loop.

Yep, those didn't work out. Let's explore why.

 

While(true) = death of a browser.

 

While(true) kills the browser. That's not good, at all. Additionally, You can't wrap the .animate() inside a loop, or make it sequential. Finding this out the hard way, I tried the following:

 

$('.anim_blk'+rand).animate({opacity:'0'},400, function(){

 

 $('.anim_blk'+rand).removeClass('back0'+cur).addClass('back0'+next).animate({opacity:'1'},600);

            });

 

This was placed inside a loop with random changing values. I also tried repeating those lines one after another several times. It did not produce the results we needed. Everything ended up changing at the same time. I realized then, that the loop was running faster than the .animate() function. I tried to delay the animation in all possible ways but still no results. The reason for this behavior? JavaScript is asynchronous by nature. I had to find out a way to chain the animation.

 

 

Overloading my brain with Google research, I had no idea that the solution was under my nose. Going back to the original code, Fito was doing exactly what he should have done; chaining the .animate() calls in a queue, thus creating an animation sequence. I had to shift my focus. Before passing out at 4:30 am I created the following code.

 

/**

 * Amedia Creative Puzzle Slider

 */

var cur = 1;

var next = 2;

 

function acPuzzleSlider(){

    var divList = Array(12);

    var total = divList.length;

    var countMarked = 0;

 

    //Initialize array to false

    for(var i=0; i<divList.length; i++){

        divList[i] = false;

    }

           

    //Enqueue the elements for animation

    while(countMarked<total){

        var rand = Math.floor(Math.random()*total);

        if(divList[rand]== false){            

            divList[rand] = $('.anim_blk'+countMarked);

            countMarked++;

        }              

    }

    //Animate doesn't work well inside loops. That's why we have to make a tail recursion

    animateDivs(divList);

    //

    cur++;

    next++;

    if(cur>4) cur = 1;

    if(next>4) next = 1;

    t = setTimeout("acPuzzleSlider()", 9600);

 

}//end acPuzzleSlider()

 

function animateDivs(divList){

    if(divList.length>0){

        value = divList.shift();

        value.animate({opacity:'0.5'}, 400, function(){

            value.removeClass('back0'+cur).addClass('back0'+next).animate({opacity:'1'}, 400, function(){

                animateDivs(divList);

            });

        });

    }

}

 

 I called this using the document ready function.

 

/*---------- Landing Animation ----------*/

window.setTimeout('acPuzzleSlider()', 900); 

 

Now my next issue, this code could not be used within a CMS fashion. The background image displayed depended on the class attribute. Although I achieved a dynamic sequence, that solution was not satisfactory. I needed to design another solution. As you may imagine, yes, I thought I could write a jQuery plugin. And that is exactly what I did on Sunday after a Mother's day party with mother.

 

/**

 * jquery.blocksCycle.js

 *

 * This plugin is used to randomly change the background of the inner blocks

 * of a container. I don't know anything about licenses.

 *

 * @author: Hisakazu Ishibashi

 * @email: h.ishibashi@amediacreative.com 

 */

(function($) {

 

    /**

     * the actual plugin

     */

    $.fn.blocksCycle = function(options) {

        

        // build main options before element iteration

        var opts = $.extend({}, $.fn.blocksCycle.defaults, options);

 

        // Iterate through the matching elements and start the animation on each of their children

        return this.each(function() {

            $this = $(this);

            

            // build element specific options

            var o = $.meta ? $.extend({}, opts, $this.data('blocksCycle')) : opts;

 

            // the blocks

            var blocks = $this.children();

            blocks = $.makeArray(blocks);

 

            if(o.shuffle==true)

                $.fn.blocksCycle.shuffle(blocks);

            

            //Animate doesn't work well inside loops, that's why we have to make a tail recursive call

            cycle(blocks, o.infinite, 0);

        });

    };

 

    /**

     * Starts the animation sequence

     */

    function cycle(blocks, infinite, bgIndex){

        var curBlocks = blocks.slice();

        var url = $.fn.blocksCycle.defaults.urls[bgIndex];

        var delay = $.fn.blocksCycle.defaults.delay;

        if(url){

            changeBlocks(curBlocks, url);

            t = setTimeout(function(){cycle(blocks, infinite, ++bgIndex)}, delay);

        }else{

            if(infinite)

                cycle(blocks, infinite, 0)

                //t = setTimeout(function(){cycle(blocks, infinite, 0)}, delay);

        }

        return;

    }

 

 

    /**

     * Used to change to the next image by changing each of the blocks sequentially

     */

    function changeBlocks(blocks, url){

        if(blocks.length>0){

            var value = $(blocks.shift());

            var rule = {

                'background': 'url('+url+')',

                'background-repeat': '',

                'background-position':''

            };

            var duration = $.fn.blocksCycle.defaults.duration;

            var opacity = $.fn.blocksCycle.defaults.opacity;

            value.animate({opacity:opacity}, duration, function(){

                value.css(rule).animate({opacity:1}, duration, function(){

                    changeBlocks(blocks, url);

                });

            });

        }

    }

 

    /**

     * This functions creates the order in which blocks will be changing to the

     * next image.

     */

    $.fn.blocksCycle.shuffle = function(list) {

        var len = list.length;

        var i = len;

        while (i--) {

            var p = parseInt(Math.random()*len);

            var t = list[i];

            list[i] = list[p];

            list[p] = t;

        }

    };

    

    //

    // plugin defaults

    //

    $.fn.blocksCycle.defaults = {

        shuffle: true,          //Indicates if the animation should be random.

        infinite: true,         //Indicates if the cycle should be infinite, it starts looping again from the first background.

        urls: null,             //List of URLs from where to get the images for animation

        first: 0,               //Indicates the first element in the backgrounds list

        /** CAUTION WHEN SETTING THE FOLLOWING VALUES**/

        delay: 16400,            //Indicates the amount of time to wait in order to show the next background

        duration: 400,          //This value is passed to jquery.anime() called inside. Indicates the duration of a single block change animation.

        opacity: 0.5            //From 0 to 1 indicates the intensity of a single block change animation.

    };

//

// end of closure

//

})(jQuery);

 

Alas! A plugin is born!

With this plugin you can configure and start the animation in the following way:

 

var images = [];

images.push('images/back02.jpg');

images.push('images/back03.jpg');

images.push('images/back04.jpg');

images.push('images/back01.jpg');

     

$.fn.blocksCycle.defaults.urls = images;

$('.slider').blocksCycle();

 

The only requirement remaining was to have the following HTML/CSS structure, and to format the background position and other attributes like background-repeat, of each block. For example:

 

<div class="slider">

<div class="block blk0"></div>

<div class="block blk1"></div>

<div class="block blk2"></div>

<div class="block blk3"></div>

<div class="block blk4"></div>

<div class="block blk5"></div>

</div>

 

The amount of inner blocks and format is totally arbitrary. Also, the div tags could be replaced by another type of container that supports the background css property. By the way, you can see in the plugin, that I made some optimizations of the first approach and in the  changeBlocks() function you can see that I replaced the add/remove Class methods for css('background').

 

Thanks for reading!

-Hisa

 

Resources:

http://www.learningjquery.com/2007/10/a-plugin-development-pattern

http://docs.jquery.com/Plugins/Authoring

 

--

Got questions?

Call us. 702 / 944 / 8744
Email us: info@amediacreative.com
Visit us: http://www.amediacreative.com
Amedia Creative is headquartered in Las Vegas, NV

We deliver highly scalable feature rich web portals & applications. We are web strategists, not just designers & developers. We’d love to create your web solution.

Share this entry

Close
Invalid e-mail format, please try again

Your friends will be notified via email about this entry.

Remove Email

You are about to remove the selected emails from your list. Are you sure you want to remove them?

Yes
Cancel
Select All Deselect All Remove Selected

Report Inappropriate Content

Close
Details
Operations

Thank You!

The safety and legitimacy of Kontain's content is paramount to us. We will be investigating this content shortly. Here is what you reported:

Report Category Harmful of Dangerous Acts Report Detail Drug Abuse
Additional Information

Lorem ipsum dolor sit amet consectetuer adipiscing elit penubrum purous

Comments (2)

To add comments please login OR signup and activate your account by clicking the activation link in the signup confirmation email.

amedia_creative

89 Updates

Designer
  • First Name:
  • Last Name:
  • Location:
  • City:
Twitter:
Website:
Bio:
Loading…
  1. dhess

    Dennys Hess


    80 Updates Updated: 8 months ago
  2. everdaniel

    Ever Daniel Barreto


    3 Updates Updated: 2 months ago
  3. joeyavino

    Joey Avino


    168 Updates Updated: 1 month ago
  4. markpalmer

    Mark Palmer


    31 Updates Updated: 14 months ago
  5. nickgoodie

    nick goodie


    129 Updates Updated: 21 days ago

Previous / Next Updates

More From Amedia

Copyright © 2010 Kontain LLC. All rights reserved