JavaScript - - By Alex Walker

JavaScript Sprite Animation Using jQuery

Last week I reported on my tests using percentage to position background-images. Today’s post is a walk thru of where I put this research to work — published earlier in the week in the July 2007 Design View.

One of the nicest things about going to a real bricks ‘n’ mortar bookstore is the effortless pleasure of thumbing mindlessly through any book that takes your fancy — not that you actually read anything in particular, but it’s just a great way to get a general sense of its layout, style, and quality.

There's something about real books...While it’s difficult to convey information about the paper and binding quality via a screen (compared to fingertips), our marketing manager Shayne and I started wondering whether it might be possible to reproduce some of that effortless “page flicking” feel, using a touch of JavaScript.

The result of our first take on the idea is here — promoting Kevin and Cam’s new JavaScript primer, fittingly enough, although not actually part of the book’s content.

Although you’re more than welcome to pick through the live version on our site to see how it’s done, I’m going to step through you through a slightly simplified, standalone version here.

It’s a little long winded, but it’s hard to cut it much shorter without leaving out potentially important stuff.

12 page sprites

Part I: The Starting Point

There were a few decisions I made early on in the project:

  1. I wanted the final product to be as flexible as possible, so that I could add or subtract pages and alter the page sizes without having to make major changes to the code.
  2. I needed all of the individual book pages (between eight and 16 pages) to be contained in a single, large, flat rendered image (like this one). This meant that I could guarantee that all my page images were downloaded and “ready to rock” at the first sign of a click.It also meant that I could overlay the rendered 3D elements of a turning page on a separate, reusable PNG layer (like the one shown below). This one is obviously setup for our blue and orange page head and footer, so you might need to generate a plain white version for use on other books.Displaying just the central third of the page gives us only the shaded curves of the inner spine — great for adding some depth to the book sitting open. When our user attempts to “turn” a page forwards or backwards, we can then align this background image to either the right- or left-hand side of our book respectively. These three simple frames go a long way toward creating the illusion of a turning page.The repeating 3D part
  3. I wanted to base my solution on jQuery, because of its easy-to-learn syntax, small size (20k), and speed.

Part II: The Structure

The markup structure we’re going to use is a construction of four divs.

1. div#leftpage: the left-hand page
2. div#rightpage: the right-hand page
3. div#flip: the midway point between the pages
4. div#turner: a wrapper to hold the whole thing together

The structure of the page turning gadget

div#flip is set as ‘position:absolute‘, so we can float it centrally over our spine region.

The other three divs are all set to position:relative, and floated to the left so that they stack together neatly. I’ve set my page dimensions to be 189px x 146px, but there’s nothing special about these dimensions — they just happened to be about the right size for the page area I had available.

Once our page inserts our images into their div backgrounds, our static book is ready, waiting for jQuery to make it dance.

Demo Button

Part III: The JavaScript

1) The setup

Time to get our hands dirty. The first thing we need to do is attach jQuery to our document and add a new script tag to contain our custom script.

 

<script type="text/javascript" src="jquery.js"></script>

<script type="text/javascript>

/* our script goes here */

</script>

2) Setting up some useful variables

We have a handful of numbers that we’ll need to use several times, so it makes sense to store them in variables. That means we’ll only need to update our script in two places if we decide to change our page size later. The variable names do a fairly good job of describing what they are:


/* Page sizes*/

var $pageheight = 189; // our page height
var $pagewidth = 146; // our page width
var $pageYpos = 0;  // current Y position of our bg-image

3) Setting up our ‘ready’ event

One of the things I like about jQuery is its simple document ready event, which is a souped-up replacement for the old-fashioned onLoad event.

Any code placed inside this ‘ready event’ will be executed as soon as the bits it needs are available. It looks something like this:


$(document).ready(function(){
// Your code here
});

The nice part is that jQuery’s ready event will begin working as soon as the parts it needs are downloaded — the old-school onLoad event had to wait until your page had completely finished loading. If you begin experimenting with jQuery some more, you’ll find yourself using this function quite a lot.

4) Making the #leftpage div clickable

The markup for our book image doesn’t contain any links, so at the moment there are no clickable “hooks” upon which to base our page-turning animation.

That’s no biggie, though — with a few lines we can tell jQuery to watch #leftpage for clicks, and then to perform a number of tasks every time it registers a click there. The code looks like this:


 $("#leftpage").click(function(){
   /* do stuff when they click on #leftpage */

});

5) Making a click re-position our background image

Okay, now we’re getting somewhere! For when a user clicks on #leftpage, we’ll first calculate how far we need to move the background image, and then we’ll move it. We set our starting Y position ($pageYpos) at the top as zero, so we just nee to add exactly one “page height” unit in order to move the background image to the next page position. We express this as $pageYpos = $pageYpos + $pageheight. After one click, the value of $pageYpos will be 189, after two clicks it will 278, and so on.

We then use that value to reposition our background image. Here’s the code snippet to perform that repositioning:


$("#leftpage").click(function(){

$pageYpos = $pageYpos + $pageheight;

 $("#leftpage").css("background-position", "0px"+$pageYpos+"px");

});

In the code above, we’re selecting #leftpage and using jQuery’s built-in css function to reposition its background graphic.

If we were to try that code now, we’d see that things are finally starting to happen! Clicking the left side of #leftpage should instantly snap the background-image to display the next page. The neat thing about this sprite technique is that our background tiles automatically — when we arrive at the last frame, the first page rolls around and the loop continues. Nice!

Demo Button

6) Showing the page turn

Adjusting background position to animate the page turn

Next we need to display the page-turning part of the graphic by telling jQuery to set the background-position of our div to the top right. It’s simple enough to add this to our click event:


$("#flip").css("background-position", "top right");

7) Removing the page turn and changing the right-hand page

If we follow the same logic, it’s not hard to predict what our next snippet of code will look like. First we need to return #flip to its default, central position:


$("#flip").css("background-position", "top center");

Next we need to display the next page on our right-hand page. We already know the new Y-position ($pageYpos) because we used it to reposition the left-hand page background earlier.


$("#rightpage").css("background-position", "146px"+$pageYpos+"px");

8) Delaying the page-turn effect

Okay, so now we have the correct animation sequence — change left-hand page, show page-turn graphic, hide page-turn graphic & change right-hand page.

The problem is, these steps all happens so quickly that we never even see the page-turning animation. We need a way to delay the script momentarily, before it clears the page-turning graphic and continues. The most common way to control time in JavaScript is via the ‘setTimeout’ function.

‘setTimeout’ requires two arguments: the action you want to take, and how long (in milliseconds) you want it to wait before taking that action. For instance, if you want an alert to pop up after five seconds, you might use the following code:


setTimeout ("alert('Five seconds has passed!)'", 5000);

All we need to do is place our last two CSS changes inside their own ‘setTimeout’ functions. I’ve set the delay on both to 200 milliseconds, but you might like to adjust it to your taste. In fact, setting the right-hand page to animate a fraction later (300 milliseconds, for example) might be closer to what happens in the real world when we turn a page in a book.


/* return flip to default position */
setTimeout ('$("#flip").css("background-position", "top center");', 200); 

/* change right page  */
setTimeout ('$("#rightpage").css("background-position", "146px "+$pageYpos+"px");', 200); 

There you have it — dropping that code into our script gives us a simple but relatively convincing animation, without having written a lot of code.

Demo Button

9) Making the right-hand page turn

In the interests of keeping this edition of this already sprawling post to a semi-reasonable length, I won’t step through the entire process of animating the right page here. Suffice to say it’s a mirror image of our left-hand page function. Instead of adding to our background Y-position, we subtract from it and we use the alternate page graphic. Have a go at working it out for yourself, or check out the finished demo here.

Demo Button

Now, if you’re thinking ‘hang on a minute, that doesn’t look quite the same as the version on sitepoint.com‘, you’re quite right. The production version:

  1. has an extra corner page curl part to the animation
  2. has an extra sliding animation effect that eases each page into place as it settles (it’s subtle, but you’ll notice it)
  3. is unobtrusive — meaning we actually load a garden-variety IMG tag into the raw page, and then use jQuery to replace it on the fly with the DIV structure outlined above. Anyone visiting the page without JavaScript enabled will at least have access to the raw page images

Of course, feel free to experiment with adding those parts into or improving the demo here. This was the our first cut, and I already have a raw concept of an improved version. Obviously there are currently built-in scaling limitations to the technique — keeping all the pages on one graphic ensures cached graphics but every page added increases the initial download requirement.

Perhaps a future version might load blocks of pages at time to strike a balance?

Sponsors