preloaded image preloaded image preloaded image

Image Animation

Ever wanted to play, pause, or reverse an animated GIF on queue but realized that you can't really do it?

Animation Direction:

forward reverse

Current Speed: 60ms delay (16.67fps)

I worked this little gem up just as a quick sample of how javascript can be used to give a clean effect of starting and/or stopping animation at a given frame. You can't do this by pairing up an animated GIF image with a static version of the same image because animated GIF images don't start cycling frames until they load, and they don't stop cycling frames until the page closes. We can't change their starting time and we can't alter the frame rate. This means that your best workaround would need to have a separate static image for each frame of the animation and then devise a way to track the time at which the GIF image was fully loaded so that you could calculate which static image to show in place of the animated GIF in order to stop the animation at a given place. If that doesn't sound too bad to you (and it should sound bad to you, by the way), then consider what your options will be in order to start or RESTART your animation from a given frame. Not such a hot idea now, is it? That would be pretty much impossible since you can't control the frame that the GIF is actually on at any given instant. The best you could do would be to delay the start/restart of the animation until it cycled around to the frame that your still shot was a copy of (which would still require you to devise a means of tracking GIF image load times and calculating which frame you are on at any given instant). So even that second-rate method has extra baggage. And how about any attempt at changing the framerate of the animation? What aboout reversing the animation entirely? Well guess what...you're stuck! You're going to need something else.

So what do we do? First, create one single "sprite" image to hold all of our animation frames in a vertical or horizontal strip (whichever makes the most sense for your image). Admittedly, this does make your image size significantly larger. But if you want to make this omelette then this is the egg we are going to have to break. My example will use a much larger image than most animated GIFs usually use so I know the file size will be almost prohibitively large in real practice. But since this is an example I don't care how long it takes for the image to load. I only care that it is pretty. For reference, there is a scaled-down version of the sprite image that is used in the animation above being trailed down the left side of this page.

scaled-down sprite image

Here is the script you will need:

var animation=function(){ var animated_item_id="animated_item"; //id of the item that will be animated var delay=40; //delay in ms to wait between moving frames var increment=350; //frame height (in this case, since we're set up for a vertical sprite) - this is the number of pixels by which the background will move per frame change var max=7700; //the maximum distance the sprite can be moved before having to loop back to the first position. This is essentially [frame height * (number of frames - 1)] var timer; //we will use this to store our setTimeout command later... var cur_pos=0; //stores the current px change to the background image at any given time var status=0; //status of 0 is stopped, status of 1 is running var min_speed=10; //I set a lower bound here for frame delay. If your application does not use speed changes then this can be ommitted. var max_speed=100; //As with min_speed, I set an upper bound here which can be removed if your application does not meddle with frame delay speeds. var speed_increment=5; //number of ms delay by which the speed adjustment controls will adjust speed - remove if not needed var start = function(){ if(status==0){ status=1; //set the status as "running" timer=setTimeout(move,delay); //create a timeout event to switch the first frame using the "move" command... } }; var stop = function(){ if(status==1){ status=0; //set the status to "stopped" } clearTimeout(timer); //clear any existing timeout event that may be waiting to fire }; var toggle = function(){ if(status==1){ animation.stop(); //if status shows the animation is running, we call the "stop" function animation.stop(); } else{ animation.start(); //else, we call the "start" function } }; var move = function(){ if(status==1){ if(cur_pos+increment>max){ cur_pos=0; //if incrementing the frame position will push us past our max adjustment it means we need to restart the loop back at the top of the sprite } else{ cur_pos+=increment; //otherwise, we set the position to the next frame's increment } if(document.getElementById(animated_item_id)){ document.getElementById(animated_item_id).style.backgroundPosition="0 -"+cur_pos+"px"; //now we actually move the background image to the next frame position } timer=setTimeout(move,delay); //and finally, we set up a timeout to trigger the next frame adjustment } }; var increase_speed = function(){ if(delay-speed_increment>=min_speed){ delay-=speed_increment; //decrease the delay by the speed increment amount, if we would not go below our minimum delay } else{ delay=min_speed; //otherwise, we just set the speed to the minimum delay value } }; var decrease_speed = function(){ if(delay+speed_increment<=max_speed){ delay+=speed_increment; //increase the delay by the speed increment amount, if we would not go above our maximum delay } else{ delay=max_speed; //otherwise, we just set the speed to the maximum delay value } }; return{ //return these to the browser window so that they will be accessible after the initial function declaration... start:start, stop:stop, toggle:toggle, increase_speed:increase_speed, decrease_speed:decrease_speed, }; }();

After that, you just need to call the appropriate animation command for your needs at the right time. For example, to make your animation start on page load:

window.onload=animation.start();

Or as part of an AJAX request, you can start the animation with the AJAX request and then stop the animation with the AJAX callback function:

function ajax_populate(target) { var url="http://www.example.com/some_url/to_pull/ajax_from.php?variable=value&another_variable=another_value"; animation.start(); if(window.XMLHttpRequest){ req = new XMLHttpRequest(); }else if(window.ActiveXObject){ req = new ActiveXObject("Microsoft.XMLHTTP"); } if(req != undefined){ req.onreadystatechange = function(){ if(req.readyState == 4){ // only if req is "loaded" if(req.status == 200){ // only if "OK" animation.stop(); var ajax_result = req.responseText; document.getElementById(target).innerHTML = ajax_result; } else{ document.getElementById(target).innerHTML="AJAX Error...\nRequest Status:\n"+ req.status + ", " +req.statusText; } } }; req.open("GET", url, true); req.send(""); }

Here is a more practical example (click to toggle animation):

blank placeholder image

So as you can see, this is pretty simple to use even in its current form. A word of warning is due, however. This script has not been set up in such a way that would make it usable for more than one item in a page. Additionally, the script assumes a vertical layout for your sprite image and it requires you to manually enter appropriate values for the frame height and the last frame's position ("max"). For these reasons, the script will need modification for each use case. Also important to mention is that this script is not set up to allow more than one item to be animated by any instance of this script. In order to use this same function across several different items in a page you would need to make some additional adjustments to the script to pass an object, class, or ID as an argument (or keep them as an array in the function) and set up more than one setTimeout command. Or you can simply paste the code in twice and change the outer function's variable name as my lazy butt has done here. You have a few choices in each approach. So, yeah it can be done. I just haven't bothered to do it for a sample script.