Animating graphics with JavaScript and DOM

Posted:


A feature I wanted for my Spycam page is an animated loop of the last 5 pictures. All the pictures are in PNG format. There are several techniques that would accomplish this: GIF animation, SVG/Flash and JavaScript. I choose to use a pure JavaScript (or ECMAScript, to be politically correct) solution, which I’ll detail in a moment. But first, I’ll briefly talk about the alternatives.

Animated GIFs have been around nearly as long as the web. Here is an example of one that I created for my late Aliens, Aliens, Aliens web site:


The GIF file format allows for multiple frames and timing instructions for the pace of animation. At first, I thought I could do the same kind of animation with PNG files, but that format does not allow animation. With image manipulation libraries, I could create a new GIF and stuff it with frames converted from the five most recent spycam photos. However, that seemed like a lot of work and I didn’t really want to create yet another file to keep track of. However, the advantage of this solution over the one I choose is that it scales better. That is, by serving a static animated GIF, taskboy.com vistors will not be continually pulling down an image for each animation loop. It’s true that most browsers cache web assets, so it’s likely that once the frames are loaded in the JS animation scheme, taskboy.com will be left relatively unmolested.

The next solution that occurred to me was to create a Flash SWF file of this loop. However, I’m not much of a Flash guy, nor am I emotionally ready to invest the time to learn the Perl interface to it. Of course, there is the XML-based, open source alternative to Flash called SVG. SVG does support animation, as you’d expect, but many browsers including Firefox do not yet support SVG animation. This was quite disappointing.

Finally, I hit on the relatively old idea of periodically switching the photo using DOM manipulation. The code to do this works in modern browsers (even IE 6). In pseudo-code, the strategy looks like this:

  1. Setup an array of images in order from oldest to newest
  2. Look for a well-known ID block and update the attributes of the child IMG tag with the next image in the array
  3. Update the description in a well-knwon ID block
  4. Schedule steps 2-3 to run again after a brief delay

Consistent browser support for DOM manipulation directly led to the Web 2.0 explosion of the early 2000s. It is possible to use Javascript to add, remove and edit nodes of the document tree in a consistent way across all major browsers. For those of you too young to remember, this was not always the case. There was an ugly time, known as the Browser Wars, that left many a young Javascript programmer mamed and bitter.

Let’s back into this code from the HTML. After all, this is the structure to be changed dynamically. Fortunately, this document structure is pretty simple:

<div id="animator" align="center">
<div align="center" id="pic_desc"></div>
</div>

This is simply a DIV block (ID = “animator”) that encloses another DIV (ID = “pic_desc”). Where’s the image tag? That will be inserted dynamically later and put ahead of the second DIV. However, an empty IMG tag could be added to the HTML right at the start. This will save some complexity in the Javascript later on, as will be noted.

The Javascript for this isn’t complex in concept, but it may look a little daunting. However, it consists only of one globally scoped array and one function.

var Pictures = new Array("2009-09-24_21-10-01.png",
                         "2009-09-24_21-11-01.png",
                         "2009-09-24_21-12-02.png",
                         "2009-09-24_21-14-01.png"
                        );

function show_next_image(last_index) {
   // Calculate the next image's index
   last_index = (last_index + 1) % Pictures.length;

   // Find right element
   var e = document.getElementById("animator");
   if (e != null) {
      var i = null;
      for (var j=0; j < e.childNodes.length; j++) {
      if (e.childNodes[j].nodeName == "IMG") {
             i = e.childNodes[j];
             break;
      }
      }

      if (i == null) {
         i = document.createElement("img");
         e.appendChild(i);
      }

      i.setAttribute("src", Pictures[last_index]);
      i.setAttribute("title", Pictures[last_index]);

      // Update description
      e = document.getElementById("pic_desc");
      if (e != null) {
         var t = null;
         if (e.childNodes.length == 0) {
             t = document.createTextNode("");
             e.appendChild(t);
         } else {
             t = e.childNodes[0];
         }
         t.data = Pictures[last_index];
      } 
   }

   setTimeout("show_next_image("+last_index+")", 5000);
}

The global array, Pictures, isn’t difficult to understand. However, how those values are generated might be somewhat non-obvious. Recall that Javascript executes in the client’s browser, which is to say, not on the server. How can the array known which files are current? The client doesn’t have access to this information. The answer is that the PHP script on the server must determine these values. The script has access to the server and can see all the available files. The PHP code then has to generate the list of filenames that appears in the rendered Javascript code. This is the sort of thing that makes CGI scripting a little mind-blowing at times. You write code in one language that generates code for another.

The function, show_next_image(), depends on the global Pictures array. It is called with index of element of Pictures used previously to update the IMG tag. The % operator may not be familiar to you. It is the modulus operator. It returns the remainder of the integer division between its operands. It has the handy property of generating values between 0 and right-hand value. So, n % 4 never generates values greater than 3. To determine the index of the new Pictures element to display, the passed-in last_index is incremented and the result modded by the length of the array. After all, it is desirable that when the last image in the array is used, the next image should be the first image in the Pictures array.

After determining which Pictures element is to be used, the DOM needs to be examined to find the right nodes to update. Here, the getElementByID() function is used to find the parent DIV. Initially, this DIV has two nodes as written: a text node containing a new line and another DIV. The enclosed child nodes are searched for one that is an IMG tag using the slightly misnamed nodeName property. If such a node is found, its reference will be stored in i. If no node reference is found (as will happen in the initial run), a new IMG node is created with createElement(). All nodes except text nodes are created with this DOM function. The new node then is added to the list of child nodes that the animator DIV has.

I need to point out to anyone following this code carefully that a small display bug has been introduced in this code. If there is no initial IMG tag, the created IMG node will appear below the description, which is undesirable. To prevent this, I simply have an empty IMG tag in the HTML about the “pic_desc” DIV. If you want a pure solution that does not require this HTML stuff, feel free to leave your solution in the comments section of this entry.

An empty IMG tag isn’t useful. An IMG node requires at least one attribute, SRC, to do something visible. The SRC attribute tells the browser where to fetch the image to be displayed. This is exactly the information that is stored in the Pictures array. Each element is the relative path to a graphic asset on my server.

Although it is possible to set node attributes with a number of different syntaxes, I recommend always using the setAttribute() function. Not only does it work with all kinds of DOM nodes in all major modern browsers, but if the attribute does not current exist in the node, it is created. This Do What I Mean behavior may be a legacy of Perl, but I can’t be sure. Certainly, I don’t see a lot of other examples in DOM where one gets a free lunch.

Once the picture is updated, the description follows. The only thing really different about this node manipulation is that it deals with a text node. Text nodes are the stuff that falls outside of HTML tags. This includes the oft-forgotten newlines that are common in hand-written HTML documents. Text nodes are created with the appropriately named createTextNode() function. The text in a Text node is updated through its data property. The pic_desc DIV should only ever have one node that is a Text node. If it has one, it is updated. If not, one is created and inserted as a child of that DIV. Then the data property is updated.

The most interesting part of this code is also the shortest bit. The DOM function setTimeout() takes two arguments: a string that represents code to be executed at some future date and delay in milliseconds that the browser will wait before executing. Notice that the code to be excuted is a string. That is, it will be eval’ed when the delay time elapses. Eval’ed code is a little weird the first time you run into it. It’s code that you write at runtime to be executed at runtime. Most code is traditionally written before compile or interpreter time. In this case, I want to schedule this function, show_next_image(), to run again. You’ll notice that this will cause the function to run forever, which is the desired effect.

A quick word about the delay setting in this function. In my testing, I found the delay value unreliable. Here, 5000 milliseconds are specified, but I believe the real-time delay between executions of this script were much shorter. Is there a way to create a more highly reliable counter? You could keep track of when time last went off and make decisions about whether to run again, add or substract a delay. If you’re trying to write a game in JS with 20 updates per second using this method, you’ve gotten on the road to perdition.

The last bit of code needed to kick off this animation extravaganza is something that initially invokes the code. In this simple example, using the onload event for body will work well.

<body onload="show_next_image(-1)">

This executes only once when the page is loaded and calls the desired function with an index before the first one. If calling show_next_image() with a -1 index seems like a cheat to you, you can rewrite the function so that it detects when it is called with a null value and assigns 0 to last_index. For me, I’ll stick with -1.

If you did not want to use DOM events to invoke this function, you could simply put a script section at the bottom of the BODY tag that called show_next_image(-1) (or its moral equivalent).

Mastering this simple DOM technique opens the way for much more sophisticated work. But that’s another post.