Newer posts are loading.
You are at the newest post.
Click here to check if anything new just came in.

January 30 2014

14:00

JavaScript Animation That Works (Part 2 of 4)

In the last post, we introduced the idea of spriting, an easy way to animate in JavaScript that works in all browsers. We also walked through how to set up the sprite as a background image for a div and then use a line of JavaScript to change the background position to make it appear as if the image has moved.

In this post, we will use this technique to animate both running and jumping motions. In order to create the animation, we will need to quickly change the background position at a regular interval. Take a look again at the sprite we are using.

javascript-spriting-j

Meet J, the mascot for my company, Joust Multimedia.

In our example, we have ten total images: one of J standing facing right, three of J running to the right and one of J jumping while facing right (with the same number of each frame facing left). Let’s start with making him run to the right. In order to make our image look like it is running, we will need to do two things: change the sprite to a different image and move the div towards the right.


Running to the Right Animation

We certainly won’t want to be stuck clicking different buttons to cycle through the sprites, so we will need to create some functions that do this automatically.

For our running function, we want to:

  1. Move the div towards the right slightly
  2. Move to the next frame of animation
  3. Pause for a fraction of a second (to preserve the “persistence of vision” illusion)
  4. Loop the function again

Fortunately, there is an easy way to loop with functions. A native command in JavaScript called setTimeout will allow us to create a timed delay, after which we will call the function again (from inside the function).

function run_right(){
  // Move slightly to the right ...
  // Change to the next frame of animation ...

  // this will call 'run_right' again after 200 milliseconds
  setTimeout(function(){run_right();}, 200); 
}

So now we have a function that will call itself again five times a second (which will be fast enough to create animation for our purposes). Remember here that browsers are not terribly accurate with their timers. You can specify timing to the millisecond, but that doesn’t mean your script will run at that timing exactly!

Our next problem to tackle is how is our function going to know which sprite to change to? In our example, we will need to cycle back and forth through our three images (to have four total frames of animation). To do this, we are going to pass our function a bit of information to tell it which slide to switch to. Once in the function, we will do a test that will check which slide we should be on, then switch the background position to the correct sprite. When we call the function again, we will pass the next slide as the argument.

function run_right(slide){
  // Move slightly to the right ...
  switch (slide){ // this switch statement checks for different possibilities for 'slide'
    case 1: // if 'slide' equals '1' ...
      document.getElementById('j').style.backgroundPosition = "-40px 0px";
      setTimeout(function(){run_right(2);}, 200);
      break;
    case 2: // if 'slide' equals '2' ...
      document.getElementById('j').style.backgroundPosition = "-80px 0px";
      setTimeout(function(){run_right(3);}, 200);
      break;
    case 3: // if 'slide' equals '3' ...
      document.getElementById('j').style.backgroundPosition = "-120px 0px";
      setTimeout(function(){run_right(4);}, 200);
      break;
    case 4: // if 'slide' equals '4' ...
      document.getElementById('j').style.backgroundPosition = "-80px 0px";
      setTimeout(function(){run_right(1);}, 200);
      break;
  }
}

And now when we call the function for the first time, we will need to make sure we pass the starting slide.

<input type="button" value="Run Right" onclick="run_right(1);" />

Similarly, to move our div to the right slightly, we can pass the initial left attribute of the div, then move the div slightly each time the function is called.

function run_right(slide, left){
  
  left = left + 15; // Increase his left attribute by 15px
  document.getElementById('j').style.left = left+"px";
  
  switch (slide){ // this switch statement checks for different possibilities for 'slide'
    case 1: // if 'slide' equals '1' ...
      document.getElementById('j').style.backgroundPosition = "-40px 0px";
      setTimeout(function(){run_right(2, left);}, 200);
      break;
    case 2: // if 'slide' equals '2' ...
      document.getElementById('j').style.backgroundPosition = "-80px 0px";
      setTimeout(function(){run_right(3, left);}, 200);
      break;
    case 3: // if 'slide' equals '3' ...
      document.getElementById('j').style.backgroundPosition = "-120px 0px";
      setTimeout(function(){run_right(4, left);}, 200);
      break;
    case 4: // if 'slide' equals '4' ...
      document.getElementById('j').style.backgroundPosition = "-80px 0px";
      setTimeout(function(){run_right(1, left);}, 200);
      break;
  }
}

And when we initially call the function, we need to make sure we pass the current left position of our div.

<input type="button" value="Run Right" onclick="run_right(1, document.getElementById('j').offsetLeft);" />

Stopping the Animation

So, now we have a function that, when called, will animate J to run to the right. Unfortunately, we have no way to stop it. First of all, we will need to make the function stop calling itself if J runs to the edge of our stage. To do that, every time the function runs, we will check an if statement to see if J has room to keep running. If so, we will run the function like normal. If not, we will stop calling the function and return him to the standing sprite.

function run_right(slide, left){
  // If we can add 15 pixels to the left and have J's right edge not be at the stage's right edge ...
  if ((left + 15) < (document.getElementById('stage').offsetWidth - document.getElementById('j').offsetWidth)){
    // We have room! Continue like normal here
  } else { // if we are on the right edge, we need to stop calling the function and return to standing
    document.getElementById('j').style.backgroundPosition = "0px 0px";
  }
}

Finally, we will want to have a way to stop the function, when needed. We can set the setTimeout() command to a variable, then stop it with the clearTimeout() command. In order to do this, we will need to declare that variable outside of the function, so that we will be able to refer to it later. For now, we will declare it as a global variable. This is terrible coding practice, but we will correct this in the next post. This is what our function looks like.

var timer;

function run_right(slide, left){
  if ((left + 15) < (document.getElementById('stage').offsetWidth - document.getElementById('j').offsetWidth)){
    left = left + 15; // Increase his left attribute by 15px
    document.getElementById('j').style.left = left+"px";
  
    switch (slide){ // this switch statement checks for different possibilities for 'slide'
      case 1: // if 'slide' equals '1' ...
        document.getElementById('j').style.backgroundPosition = "-40px 0px";
        setTimeout(function(){run_right(2, left);}, 200);
        break;
      case 2: // if 'slide' equals '2' ...
        document.getElementById('j').style.backgroundPosition = "-80px 0px";
        setTimeout(function(){run_right(3, left);}, 200);
        break;
      case 3: // if 'slide' equals '3' ...
        document.getElementById('j').style.backgroundPosition = "-120px 0px";
        setTimeout(function(){run_right(4, left);}, 200);
        break;
      case 4: // if 'slide' equals '4' ...
        document.getElementById('j').style.backgroundPosition = "-80px 0px";
        setTimeout(function(){run_right(1, left);}, 200);
        break;
    }
  } else {
    document.getElementById('j').style.backgroundPosition = "0px 0px";
  }
}

And we can create another function to stop the running timer and return the sprite to the standing image.

function stop_running(){
  document.getElementById('j').style.backgroundPosition = "0px 0px";
  clearTimeout(timer);
}

Running to the Left Animation

Now by borrowing the code from our run_right function, we can create another function to make a run_left function, with just a few modifications.

function run_left(stage, left){
  if ((left - 15) > 0){
    left = left - 15;
    document.getElementById('j').style.left = left+"px";
    switch (stage){
      case 1:
        document.getElementById('j').style.backgroundPosition = "-40px -50px";
        timer = setTimeout(function(){run_left(2, left);}, 200);
        break;
      case 2:
        document.getElementById('j').style.backgroundPosition = "-80px -50px";
        timer = setTimeout(function(){run_left(3, left);}, 200);
        break;
      case 3:
        document.getElementById('j').style.backgroundPosition = "-120px -50px";
        timer = setTimeout(function(){run_left(4, left);}, 200);
        break;
      case 4:
        document.getElementById('j').style.backgroundPosition = "-80px -50px";
        timer = setTimeout(function(){run_left(1, left);}, 200);
        break;
    }
  } else {
    document.getElementById('j').style.backgroundPosition = "0px -50px";
  }
}

Jumping Animation

Finally, we need to create a jump function. We will pass two arguments to this function, one that will track whether the div is currently moving up or down and another that will track the current top attribute of the div. Between the two, we will determine which direction the div needs to move next, and how far (we will move the div less distance near the arc of the jump to simulate acceleration with gravity).

function jump(up, top){
  /*
   * We change J to his jumping sprite ...
   */
  document.getElementById('j').style.backgroundPosition = "-160px 0px";
  /*
   * Here, we need to decide whether he should be traveling up or down...
   */
  if (up && (document.getElementById('j').offsetTop > 20)){
    // if he is currently moving up, and he is more than 20 pixels from the top of the stage ...
    top = top - (top * .1); // This gives us a slight arc in the jump, rather than a constant movement like running
    document.getElementById('j').style.top = top+"px"; // Change his position
    timer = setTimeout(function(){jump(up, top);}, 60); // Then call the function again
  } else if (up) {
    // if he is currently moving up, but he is almost at the top of the stage and needs to come back down...
    up = false; // we switch the 'up' variable so he will be falling in the next loop
    timer = setTimeout(function(){jump(up, top);}, 60);
  } else if (!up && (document.getElementById('j').offsetTop < 115)){
    // if he is moving down, but is more than 5px from the ground, he will continue to fall...
    top = top + (top * .1); // His fall will slightly accelerate
    document.getElementById('j').style.top = top+"px";
    timer = setTimeout(function(){jump(up, top);}, 60);
  } else {
    // If he is moving down, and he is within 5px of the ground...
    document.getElementById('j').style.top = "120px"; // Place him on the ground
    document.getElementById('j').style.backgroundPosition = "0px 0px"; // return to standing sprite
    // We do not call the loop anymore since he is standing still at this point
  }
}

Now we can put all four of our functions into buttons and have a working prototype of a running and jumping animation! Please check out the source code for this page with comments and download the sprite sheet that I used, if you’d like.


Conclusion

Now, although we have a working prototype here, you may notice it is a little buggy. When you click on more than one button at a time, the script will try to run both at once. Or, if you click the jump button again on the way down, J will continue to fall forever. Also, as I mentioned earlier, we have global variables in our script, which means it might be difficult to add this code into an existing page without crashing other JavaScript (which is also why I didn’t try to run this code within this blog page). In our next post, we will clean up all of these bugs and talk about the concept of encapsulation and why it is important to write good code in the real world.

January 14 2014

18:31

JavaScript Animation That Works (Part 1 of 4)

HTML is the language the web is built in, and it is kind of a strange beast. Although it was originally intended as a way to easily share academic information across the Internet, it has been slowly transformed to accommodate the media-rich environment we know and love, sort of like a cherished lawn mower that has been fitted with jet engines.

Because of the haphazard nature of HTML (and JavaScript, the programming language that manipulates elements in HTML and makes them interactive), sometimes we have to think outside the box a bit. In this tutorial series, I will show you how to do cross-browser animation using a method called spriting, using only JavaScript and HTML (and a bit of CSS too). And, because this is a learning opportunity (and I am stubborn and like to do things the hard way), we will be doing it all without any external libraries (like jQuery).

This will be a four-part series – I will explain the spriting itself in part one (this article) with some basic JavaScript, but then in later installments we will move into some intermediate techniques like encapsulation, event handling, and touch screen support.

So let’s get started!


What Is Animation?

Animation is based on a phenomenon called persistence of vision, which basically says that if your brain sees lots of similar still images fast enough, then it will appear as though it is one moving image. Every kind of film or video uses this basic technique – many, slightly different frames are shown in quick succession to make something appear moving. Film typically runs at 24 frames per second (₁), while broadcast television in North America is shown at 29.97 frames per second (₂). So, in other words, what we want to do is create something that shows similar frames really quickly (several times a second).


The Difficulties on the Web

There are two main reasons animation is difficult to use on the web:

  1. The first is that different web browsers have different ways they interpret HTML and JavaScript, so what works on one device often doesn’t work on another. Flash works great on most browsers, but support is starting to drop for it and iOS devices won’t allow it at all. Canvas has a lot of potential, but Internet Explorer 8 doesn’t support it. Same goes with Adobe Edge Animate. GIFs work on everything, but you can’t control the animation or make it interactive.
  2. And for the second, every time an image is served on a web page, a separate request is made between the browser and server. These requests can add up, even over a lightning fast Internet connection, making having several frames each second unmanageable.

The Solution: Spriting

One way around these problems is to make a sprite sheet. In elements like div‘s, we can set a background image for the div that may be larger than the element itself. We can also set the background position so that we determine exactly which part of the larger image will show. A sprite sheet is a larger image made of several smaller images that we can move around so that it could take the place of many small images. Take a look at the example below, using J, the mascot of my company Joust Multimedia:

javascript-spriting-j

Although there are ten different images of J, they are placed together on one larger PNG file (we are using PNGs because they can show transparency). If we have a div that is only the size of one of the images, and we set this PNG as the background, it will look like a single image.

See the Pen hazdm by Steven Riche (@StevenRiche) on CodePen.

Although this seems like a lot of trouble to go through to show an image, this method nicely fixes the two issues we had before. With very little JavaScript (one line!), you can change the background position of a div, and it works on everything. Also, since all of these frames are on the same image, it will only take one request to load that image in the page. So, once the page loads, it can switch between sprites with no problem at all.

So how do we set this up to animate easily then? The first step is to create the sprite sheet. You will want to know what the final dimensions of your image should be, and space the sprites accordingly in a grid. For instance, my J image is going to be 40px wide by 50px tall, so I lined up my sprites exactly 40px apart horizontally and exactly 50px apart vertically. It will probably be easiest if you set your starting sprite in the upper left corner.

Then we will set up a div with a bit of CSS to make sure that everything appears properly.

<div id="stage">
	<div id="j" class="character"></div>
</div>

And here is our CSS to make sure the sprite is showing correctly:

.character {
	/*
	 * Very important that we set the height and width of 
	 * our characters to the height and width of the sprites
	 */
	height:50px;
	width:40px;
	/*
	 * We need to position them absolutely so we can have full  
	 * control over their position within the stage
	 */
	position:absolute;
	left:100px;
	top:120px;
}

#j {
	/*
	 * And now we set the background image for the character div 
	 * to be the first sprite (in the upper left corner)
	 */
	background-image: url('j.png');
	background-repeat:no-repeat;
	background-position:0 0;
}

Notice the following things:

  • We specify the width and height of the div to the size of our sprite
  • We specify the background-repeat to 'no-repeat'
  • We specify the background-position to '0 0' – this will show the sprite in the top left corner

Now, it will just take a single line of JavaScript to change the background position to show the next sprite.

document.getElementById('j').style.backgroundPosition = '-40px 0px';

Here, we are selecting the element (with id='j'), and setting the style attribute 'backgroundPosition'. Notice that it is spelled 'backgroundPosition' in JavaScript, and not like 'background-position' in CSS. Also notice that in JavaScript, the 'px' is required for both the x and y amount – we cannot just pass it numbers. And because we are moving the background image, we have to move it in the opposite direction from what you might expect – to move to the sprite on the right, we have to have the image move 40px to the left.

Now, if we just have something simple to execute this code (like a button), we can see the frames changing in action.

See the Pen DIsgk by Steven Riche. Check out the source code for this page. It has all of the examples here with thorough comments. And here is a link to the sprite sheet I am using.

Next Up

This is still not really animation though – in part two of this series we will actually animate some running and jumping, by creating loops with the different sprites.

By part four, we will be creating mouseovers for a bit of robot action.

See the Pen ByGtv by Steven Riche.


In Conclusion & Drawbacks

Although this can be a great method for animating on the web, there are a few drawbacks. First, it can require you to create each individual frame of animation, which can be time-consuming. Second, browsers don’t have the most accurate of timers for animation, so if it is critical for your animation to be timed perfectly, then this may not work. Finally, mobile Safari (used on iPhones and iPads) has a “feature” where if you have a background image that is either larger than 2MB or larger than 1024 X 1024 X 3 pixels (or 3,145,728 total pixels), it will automatically rescale the image, ruining the spriting effect. This means that really large sprites, or animations with a very large number of sprites, is out of the question. But, for simple, small animations that you want to be very interactive, this is an easy and great way to get something that works everywhere.

Interesting Side Notes

1: Before sound was introduced with film, there wasn’t really a standard frame rate. The cameras were operated by a hand crank, so if you had a rookie cameraman the frame rate might slow down and speed up dramatically unintentionally. Similarly, less reputable theaters were notorious for telling their projectionists to crank the projector quicker to speed up the show so they could fit in more screenings. This is also why we stereotypically think of pre-sound movies as moving around comically fast – most were filmed around 16-18 fps, so when we play them today at 24 frames per second, they are moving faster than they were originally intended.

2: Television was originally broadcast at 30 fps in North America, but color television caused a glitch when shown at that speed. Engineers figured out that they could fix it by slowing down the frame rate by 0.1%, which is why it is now set at 29.97 fps. Also, besides all of the wacky technical issues involved with converting a film in 24 fps to display on television at 29.97 fps, showing television at a faster fps has had an interesting effect on the public. Many people watching the test screenings of “The Hobbit” at 48 fps reported that the increased frame rate made the film look “cheaper”, even though it was much higher quality than a typical film, just because they had grown to associate faster frame rates with watching something on television.

Older posts are this way If this message doesn't go away, click anywhere on the page to continue loading posts.
Could not load more posts
Maybe Soup is currently being updated? I'll try again automatically in a few seconds...
Just a second, loading more posts...
You've reached the end.
Get rid of the ads (sfw)

Don't be the product, buy the product!

Schweinderl