J R Sinclair

Animation with SVG and Javascript

Part 2: Making the Smiley Bounce

We're going to make the smiley move using javascript. The basic idea is that we want to move the smiley just a tiny bit, but do it really quickly, lots of times so that the smiley looks like it's moving smoothly. First of all though, we need to put a <script> in the file so that we have somewhere to write our javascript. So, add these lines to the file, after the <svg> element but before the <rect> element:

 <!-- Script to do the animation -->
 <script type="text/javascript"><![CDATA[


 ]]></script>

Pretty much all of our javascript will go in between those lines of code.

Next, we'll need some variables to keep track of where smiley is. Variables are like letters in algebra. They represent something like a number, but you don't really care what the exact value is. We'll need one to keep track of how far across our smiley is (we'll call it x), and we'll need one to keep track of how far down our smiley is (we'll call it y). We'll also need variables to keep trak of how fast the smiley is going across (vx) and how fast it's going down (vy).

To get variables in javascript, we type var, then we type the name of the variable. Oh, and by the way, nearly everything in javascript has to have a semi-colon ";" at the end of it. It's kind of like a full-stop to say "this bit's done, let's go on to the next bit."

So the code for our variables will look something like this:

   var x;
   var y;
   var vx;
   var vy;

Next, we'll need a variable to tell the computer how often to re-draw the smiley's position. Animations on TV usually do this about 30 times a second, so we want to refresh every 0.033, seconds (that's 1/30). The computer does things in milliseconds though, so we need to multiply that by 1000. So we want the computer to refresh the smiley every 33 miliseconds. We'll add another variable called interval to represent this.

    var interval = 33;

We also need the script to know how high and how wide the picture is, and what the diameter of the ball is, so we'll add another couple of variables for those. Javascript lets us put in values for them at the same time we create them by adding an = sign.

  var docHeight = 400;
  var docWidth = 400;
  var ballDiam = 60;

Finally, we want a variable to represent the SVG document we're editing, since we make the smiley move by changing the SVG. We also want a variable that will represent the smiley face group that we created in the SVG document.

  var svgdoc;
  var smileyFace;

OK. Now we want to set up the smiley so that it does something when we click it. To do this, we go back to the group (<g>) element and add an attribute called onclick. Add an onclick attribute so that the opening <g> tag looks like the following:

 <g id="smileyFace" transform="translate(3,3)"
    onclick="alert('Greetings Earthling');">

If you load your picture in a viewer now, and click on the smiley you should get an alert box saying "Greetings Earthling".

An alert box saying 'Greetings Earthling'.

Obviously, we don't want it to throw up an alert box all the time, we want it to move. To do this, we have to write a function to tell the viewer to start the animation. Back in our script block, put in the following:

 var intervalTimer = -1;

 // This function starts the animation
 function startAnim() {
   var svgdoc = evt.getCurrentNode().getOwnerDocument();
   smileyFace = svgdoc.getElementById("smileyFace");
   if (intervalTimer == -1) {
     intervalTimer = setInterval("nextFrame()", interval);
   }
   y = 0;
   vy = 0;
   x = 0;
   vx = 0.5;
 }

Once again, let's try and break this up a bit…

var intervalTimer = -1; This is yet another variable to keep track of the timer that tells the screen to change every 33 milliseconds.
// This function starts the animation This is a comment to tell anyone reading the code what the function does. There are two ways of doing comments in javascript. You can either put two forward slashes // at the beggining of a line like here, or, you can put your comments between a slash and a star: /* … */
function startAnim() { This line says that we want to create a function called "startAnim".
svgdoc = evt.getCurrentNode().getOwnerDocument(); This makes our svgdoc variable actually represent the SVG that we wrote. Don't worry too much about how it does this at the moment.
smileyFace = svgdoc.getElementById("smileyFace"); This is the variable to represent the smiley face. What we've essentially done is tell the viewer "go through the SVG we wrote until you find the element with an id of 'smileyFace', then make this variable represent that."
if (intervalTimer == -1) {
  intervalTimer = setInterval("nextFrame()", interval);
}
This piece of code says: "if intervalTimer still equals -1, then set up a timer that goes off every interval milliseconds and runs the function called nextFrame. Then make the variable intervalTimer represent this timer instead of being equal to -1." Now we need to write a nextFrame function to do the actual moving of the smiley face.
y = 0;
vy = 0;
x = 0;
vx = 0.5;
These set the position and speed of the smiley when it starts moving. y = 0 puts the smiley at the top of the screen. The vy = 0 says that when we start the smiley doesn't have any speed downward, as if we had just lifted it up there and let it go. For the very tiniest fraction of a second, it has no speed. The x = 0 puts the smiley at the left-hand side of the screen. The vx = 0 says that to start with the ball is moving to the right at a speed of 1.5 ( the "v" in "vx" stands for velocity, which means 'speed in just one direction').
} This says that the function finishes here.

Next, we need to write the function to move the smiley. Put this in the code after the startAnim function but before the ]]></script> bit.

 // This function moves the smiley for each frame
 function nextFrame() {
  y = y + 1;
  x = x + 1;
  var transform = "translate(" + x + "," + y + ")";
  smileyFace.setAttribute("transform", transform);
 }

We also need to change the onclick event so that it calls our startAnim function instead of bringing up the alert box. Change the <g> tag to look like the following:

 <g id="smileyFace" transform="translate(3,3)"
    onclick="startAnim();">

Load this up in the viewer and click on the smiley to see what it does. The smiley should move diagonally down and to the right. This is because we've told it to move one pixel down and one pixel across every 33 milliseconds. You'll notice that it completely ignores the edges of the picture and just keeps on going until you can't see it– it certainly doesn't bounce off them. It doesn't look like it's falling either because it's just moving at a constant speed. So, we need to make it look like it's falling and tell it about the walls so it will bounce.

First of all, falling. Change the nextFrame function to look like this:

 var g = 9.8 / 2800;

 // This function moves the smiley for each frame
 function nextFrame() {
  vy = vy + g*interval;
  var y_next = y + vy*interval;
  y = y_next;
  x = 0;

  var transform = "translate(" + x + "," + y + ")";
  smileyFace.setAttribute("transform", transform);
 }

Load the picture up in the viewer again and click the smiley to see what it does. It should look like it's falling now, but it will very quickly fall off the bottom of the screen. Let's have a look at the changes that made this happen.

var g = 9.8 / 2800; g stands for gravity. Everything on earth falls at the same rate of 9.8 meters per second squared (m.s-2). We divide this by 2800 to get a rough conversion from metres to pixels.
vy = vy + g*interval; This is where we tell the smiley to get faster and faster as it falls, just like a ball would if you dropped it from a height. The speed it's falling in this frame is the speed it was falling in the last frame, plus g times the amount of time that passed since the last frame.
var y_next = y + vy*interval;
y = y_next;
These lines work out what the next y-position of the smiley should be if it's moving at a speed of vy.

OK. Now let's make the smiley bounce. To do this we'll have to change our nextFrame function again.

 var g = 9.8 / 2800;
 var wallDamp = 0.95;

 // This function moves the smiley for each frame
 function nextFrame() {
   vy = vy + g*interval;
   y_next = y + vy*interval;
   if (y_next < (docHeight - ballDiam)) {
     y = y_next;
   } else {
     vy = -wallDamp*vy;
     y = (docHeight - ballDiam);
   }

  var transform = "translate(" + x + "," + y + ")";
  smileyFace.setAttribute("transform", transform);
 }
 

If you have a look at the picture now and click on the smiley, then it should fall and bounce when it hits the bottom of the picture. We'll take a look at the changes that made it do this.

var wallDamp = 0.95; When a ball bounces it doesn't bounce to the height that you dropped it from, it gets slightly lower with each bounce. This is because the ball loses some of its energy each time it bounces. We use this variable to set how much energy the ball gets to keep, each time it bounces. With the wallDamp set at 0.95, it gets to keep 95% of its energy and loses 5% each time it bounces. Try setting it to different values to see what happens.
if (y_next < (docHeight - ballDiam)) {
  y = y_next;
} else {
  vy = -wallDamp*vy;
  y = (docHeight - ballDiam);
}
This is where we make the ball bounce. It says, if the smiley is not about to fall off the bottom of the picture, then just let it keep falling. Otherwise reverse the direction the ball is travelling, reduce it's speed by the amount wallDamp, and make it's position look like it's just hit the bottom.

OK. So our ball bounces, but it still looks pretty boring. It doesn't move from side to side at all. To make this happen we have to change the vx and x variables. We'll change our nextFrame() function again to look like this:

 var g = 9.8 / 2800;
 var wallDamp = 0.95;

 // This function moves the smiley for each frame
 function nextFrame() {
 
   // Work out the y-position
   vy = vy + g*interval;
   y_next = y + vy*interval;
   if (y_next < (docHeight - ballDiam)) {
     y = y_next;
   } else {
     vy = -wallDamp*vy;
     y = (docHeight - ballDiam);
   }
   
   // Work out the x-position
   vx = 0.98*vx;
   var x_next = x + vx*interval;
   if (x_next > docWidth - ballDiam) {
     vx = -wallDamp * vx;
     x = docWidth - ballDiam;
   } else if (x_next < 0){
     vx = -wallDamp * vx;
     x = 0;
   } else {
     x = x_next;
   }

  var transform = "translate(" + x + "," + y + ")";
  smileyFace.setAttribute("transform", transform);
 }
 

Let's have a look at what we added:

// Work out the x-position This is a comment to explain what the code is doing.
vx = 0.98*vx; This works out how fast the ball is travelling in the x-direction, i.e. horizontally.
var x_next = x + vx * interval; Work out how far the ball will be from the left-hand side of the picture.
if (x_next > docWidth - ballDiam) { If the ball is about to hit the right-hand wall, then
  vx = -wallDamp * vx;
  x = docWidth - ballDiam;
Reverse the direction the smiley is travelling and reduce its speed by wallDamp. Make sure the smiley doesn't go outside the borders by setting its x-position so that it just touches the right edge.
} else if (x_next < 0){ Otherwise, if the smiley is about to hit the left wall, then
  vx = -wallDamp * vx;
  x = 0;
Reverse the direction the smiley is travelling and reduce its speed by wallDamp. Make sure the smiley doesn't go outside the borders by setting its x-position so that it just touches the left edge.
} else {
  x = x_next;
}
Otherwise (i.e. if the smiley isn't about to hit either wall), just let the smiley keep going.

And you're finished. If everything has gone to plan, your <script> tag should look something like this:

 <!-- Script to do the animation -->
 <script type="text/javascript"><![CDATA[
  var x;
  var y;
  var vy;
  var vx;
  var interval = 33;
  var docHeight = 400;
  var docWidth = 400;
  var ballDiam = 60;
  var intervalTimer = -1;
  var svgdoc, smileyFace;

 // This function starts the animation
 function startAnim() {
   svgdoc = evt.getCurrentNode().getOwnerDocument();
   smileyFace = svgdoc.getElementById("smileyFace");
   if (intervalTimer == -1) {
     intervalTimer = setInterval("nextFrame()", interval);
   }
   y = 0;
   vy=0;
   x=0;
   vx=0.5;
 }


 var g=9.8/2800;
 var wallDamp = 0.95;

 // This function moves the smiley for each frame
 function nextFrame() {
   
   // Work out the y-position
   vy = vy + g*interval;
   var y_next = y + vy*interval;
   if (y_next < (docHeight - ballDiam)) {
     y = y_next;
   } else {
     vy = -wallDamp*vy;
     y = (docHeight - ballDiam);
   }
   
   // Work out the x-position
   vx = 0.98*vx;
   var x_next = x + vx*interval;
   if (x_next > docWidth-ballDiam) {
     vx = -wallDamp*vx;
     x = docWidth-ballDiam;
   } else if (x_next < 0){
     vx = -wallDamp*vx;
     x = 0;
   } else {
     x = x_next;
   }
   
   var transform = "translate(" + x + "," + y + ")";
   smileyFace.setAttribute("transform", transform);

 ]]></script>
 

If you want to experiment, try changing the values for vx, vy or wallDamp. To start with, you might want to keep wallDamp between 0 and 1, but feel free to try other values once you've got a feel for it.

THE END.