5 out of 8 rated this helpful - Rate this topic

Basic SVG animation

This topic covers basic SVG animation and is a prerequisite for Intermediate SVG Animation. Basic knowledge of HTML and JavaScript is assumed. To fully understand the material presented in this topic, plan to spend about one hour of study.

Note  In order to view the examples contained within the topic, you must use a browser, such as Windows Internet Explorer 9, that supports the SVG element.

Although not supported in Internet Explorer 9 (as described below), basic animation is straightforward when you use SVG’s declarative animation constructs ( SMIL). For example, the following code rotates a square 90 degrees over a five second interval:

Example 1 - Basic Declarative (SMIL) Animation

Live link: Example 1 (Be aware that, as explained later in this topic, this particular example will not function in Internet Explorer 9)


<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

<svg width="800px" height="800px" viewBox="0 0 800 800"
     version="1.1" xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"> <!-- Note that this is required in order to use xlink in the <use> element. -->

  <!-- THIS EXAMPLE NOT SUPPORTED IN INTERNET EXPLORER 9 -->
  
  <title>Simplest SVG Animation</title>
  <desc>SVG declarative animation is used to rotate a square.</desc>

  <g transform="translate(400, 400)"> <!-- Create a Cartesian coordinate system (with the y-axis flipped) for the animated square. That is, place the origin at the center of the 800 x 800 SVG viewport. --> 
  
    <!-- A 200 x 200 square with the upper left-hand corner at (-100, -100). This places the center of the square 
    at the origin (0, 0): -->  
    <rect x="-100" y="-100" width="200" height="200" rx="5" ry="5" 
          style=" fill: orange; stroke: black; stroke-width: 3; stroke-dasharray: 10, 5;">
      <animateTransform 
        attributeType="xml"
	    attributeName="transform" type="rotate"
	    from="0" to="90"
	    begin="0" dur="5s" 
	    fill="freeze"
      />
    </rect>
    
    <line x1="-400" y1="0" x2="400" y2="0" style="stroke: black;" /> <!-- Represents the x-axis. -->
    
    <line x1="0" y1="-400" x2="0" y2="400" style="stroke: black;" /> <!-- Represents the y-axis (although up is negative and down is positive). -->  
        
  </g>
</svg> 

The previous example (as well as all the following examples) are well commented. However, a few helpful things to point out include:

  • The animateTransform element, child of the object we want to animate (that is, rect), does all the heavy lifting and is relatively self-explanatory.

  • Because the square is positioned such that its center coincides with the origin of the viewport, at (400, 400), the square is rotated 90 degrees about its center. If, for example, the square were defined with x=”0” and y=”0”, as in:

    
    <rect x="0" y="0" width="200" height="200" rx="5" ry="5" style=" fill: orange; stroke: black; stroke-width: 3; stroke-dasharray: 10, 5;">
    
    

    Then the square’s upper left-hand corner (as opposed to its center) would rotate 90 degrees about the origin. Give it a try!

Although declarative animation is straightforward, it can also be limiting. In the words of David Eisenberg, author of SVG Essentials: "If you choose to use scripting to do animation, you will then have available all the interactivity features of scripting; you can make animation dependent on mouse position or complex conditions involving multiple variables."

That is, script-based animation opens the door to simple as well as complex animation possibilities. Because of this, and for other reasons (such as CSS animations), IE9 does not support declarative animation. Undeniably, there can be more work associated with script-based animation, but once these scripting techniques have been mastered, you can implement animations impossible using purely declarative animation techniques. The following example, which is the JavaScript version (in HTML5) of Example 1, shows some of these techniques:

Example 2 - Basic JavaScript Animation

Live link: Example 2


<!DOCTYPE html>
<html>

<head>  
  <title>JavaScript SVG Animation</title>
  <!--  <meta http-equiv="X-UA-Compatible" content="IE=Edge"/> Remove this comment only if you have issues rendering this page on an intranet site. -->
  <style>
    /* CSS here. */
  </style> 
  <script>  
    /* CONSTANTS */
    var initialTheta = 0; // The initial rotation angle, in degrees.
    var thetaDelta = 0.3; // The amount to rotate the square every "delay" milliseconds, in degrees.
    var delay = 10; // The delay between animation stills, in milliseconds. Affects animation smoothness.
    var angularLimit = 90; // The maximum number of degrees to rotate the square.
    
    /* 
      Note that it will take the square (angularLimit/thetaDelta)*delay milliseconds to rotate an angularLimit
      number of degrees. For example, (90/0.3)*10 = 3000 ms (or 3 seconds) to rotate the square 90 degrees.
    */
    
    /* GLOBALS */
    var theSquare; // Will contain a reference to the square element, as well as other things.
    var timer; // Contains the setInterval() object, used to stop the animation.
    
    function init()
    /*
      Assumes that this function is called after the page loads.
    */
    {
      theSquare = document.getElementById("mySquare"); // Set this custom property after the page loads.
      theSquare.currentTheta = initialTheta; // The initial rotation angle to use when the animation starts, stored in 
      timer = setInterval(doAnim, delay); // Call the doAnim() function every "delay" milliseconds until "timer" is cleared.     
    }
    
    function doAnim()
    /*
      This function is called by setInterval() every "delay" milliseconds.
    */
    { 
      if (theSquare.currentTheta > angularLimit)
      {
        clearInterval(timer); // The square has rotated enough, instruct the browser to stop calling the doAnim() function.
        return; // No point in continuing; stop now.
      }
      
      theSquare.setAttribute("transform", "rotate(" + theSquare.currentTheta + ")"); // Rotate the square by a small amount.
      theSquare.currentTheta += thetaDelta;  // Increase the angle that the square will be rotated to, by a small amount.
    }
  </script>  
</head>

<body onload="init(); doAnim();"> <!-- init() sets up the animation, doAnim() actually does the animation. -->
  <svg width="800px" height="800px" viewBox="0 0 800 800">
    <g transform="translate(400, 400)"> <!-- Create a Cartesian coordinate system (with the y-axis flipped) for the animated square. That is, place the origin at the center of the 800 x 800 SVG viewport: -->
  
      <!-- A 200 x 200 square with the upper left-hand corner at (-100, -100). This places the center of the square 
      at the origin (0, 0): -->  
      <rect id="mySquare" x="-100" y="-100" width="200" height="200" rx="5" ry="5" 
            style=" fill: orange; stroke: black; stroke-width: 3; stroke-dasharray: 10, 5;" />
            
      <!-- Represents the x-axis: -->
      <line x1="-400" y1="0" x2="400" y2="0" style="stroke: black;" /> 
    
      <!-- Represents the y-axis (although up is negative and down is positive): -->  
      <line x1="0" y1="-400" x2="0" y2="400" style="stroke: black;" /> 
                
    </g>
  </svg>
</body>

</html>

Important  As opposed to including <meta http-equiv-"X-UA-Compatible" content="IE-9" /> or <meta http-equiv-"X-UA-Compatible" content="IE-Edge" /> within the <head> block, you can configure your web development server to send the X-UA-Compatible HTTP header with IE=Edge to ensure that you are running in the latest standards mode, if you are developing on an intranet.

Script-based animation is actually relatively straightforward if you understand the basics of traditional “cartoon” animation. Animation, as you probably know, is simply a series of still images, each of which is changed incrementally, shown one right after another in quick succession:

Animation stills

If these six images are successively shown quickly enough, the eye will see a bouncing ball:

bouncing ball animated GIF file

In this case, the bouncing ball animation is produced by repeatedly showing the six images such that each image is displayed for 100 milliseconds before moving on to the next image. The same concept is used in script-based animation. In code for Example 2, we simply call a function that incrementally changes an image every few milliseconds. In particular, we use setInterval to tell the browser to invoke a function, doAnim, every delay milliseconds. The doAnim function rotates the square by a small amount each time it is called. Because doAnim is called every few milliseconds, the square appears to rotate. After the square has rotated angularLimit number of degrees (90° in the example), we tell the browser to stop calling doAnim by clearing the timer variable, and the animation stops (see the code comments for more information).

Before moving on to more complex examples, it’s important to point out two styles of JavaScript coding:

  • DOM L2 Scripting, and
  • SVG DOM Scripting

DOM L2 Scripting is “traditional” scripting and is exemplified by building “value strings” to set various items, as in:


theSquare.setAttribute("transform", "rotate(" + theSquare.currentTheta + ")");

SVG DOM scripting is exemplified by the lack of such “value strings,” and typically sets element values numerically, as in:


mySquare.transform.baseVal.getItem(0).setRotate(mySquare.currentTheta, 0, 0);

Both lines do exactly the same thing. The only minor difference is that the setRotate method requires two values indicating a center point in which to rotate the object about (center point (0, 0) in this case). The advantage of the SVG DOM scripting style is that you do not have to build “value strings” and it can be more performant than DOM L2 scripting.

The following, Example 3, is the SVG DOM scripting version of Example 2:

Example 3 - SVG DOM Scripting

Live link: Example 3


<!DOCTYPE html>
<html>

<head>  
  <title>JavaScript SVG Animation</title>
  <!--  <meta http-equiv="X-UA-Compatible" content="IE=Edge"/> Remove this comment only if you have issues rendering this page on an intranet site. -->
  <style>
    /* CSS here. */
  </style> 
  <script>  
    /* CONSTANTS */
    var initialTheta = 0; // The initial rotation angle, in degrees.
    var thetaDelta = 0.3; // The amount to rotate the square every "delay" milliseconds, in degrees.
    var delay = 10; // The delay between animation stills, in milliseconds. Affects animation smoothness.
    var angularLimit = 90; // The maximum number of degrees to rotate the square.
    
    /* 
      Note that it will take the square (angularLimit/thetaDelta)*delay milliseconds to rotate an angularLimit
      number of degrees. For example, (90/0.3)*10 = 3000 ms (or 3 seconds) to rotate the square 90 degrees.
    */
    
    /* GLOBALS */
    var timer; // Contains the setInterval() object, used to stop the animation.
    
    function init()
    /*
      Assumes that this function is called after the page loads.
    */
    {
      var transformObject;
      
      mySquare.currentTheta = initialTheta; // The initial rotation angle to use when the animation starts.
      timer = setInterval(doAnim, delay); // Call the doAnim() function every "delay" milliseconds until "timer" is cleared.     
      transformObject = svgElement.createSVGTransform(); // Create a generic SVG transform object so as to gain access to its methods and properties, such as setRotate().
      mySquare.transform.baseVal.appendItem(transformObject); // Append the transform object to the square object, now the square object has inherited all the transform object's goodness.
    }
    
    function doAnim()
    /*
      This function is called by setInterval() every "delay" milliseconds.
    */
    { 
      var transformObject;
      
      if (mySquare.currentTheta > angularLimit)
      {
        clearInterval(timer); // Instruct the browser to stop calling the function indicated by setInterver();
        return;
      }
      
      mySquare.transform.baseVal.getItem(0).setRotate(mySquare.currentTheta, 0, 0); // Access the transform object (that was appended to mySquare in the init() function) and use its setRotate method to rotate the square about the point (0, 0) (which is at the center of the SVG viewport).
      mySquare.currentTheta += thetaDelta; // Place this line here so that the square isn't over rotated on the last call to doAnim().
    }
  </script>  
</head>

<body onload="init(); doAnim();"> <!-- init() sets up the animation, doAnim() actually does the animation. -->
  <svg id="svgElement" width="800px" height="800px" viewBox="0 0 800 800"> <!-- Give the svg element a name so that we can easily access it via JavaScript. -->

    <g transform="translate(400, 400)"> <!-- Create a Cartesian coordinate system (with the y-axis flipped) for the animated square. That is, place the origin at the center of the 800 x 800 SVG viewport: -->
  
      <!-- A 200 x 200 square with the upper left-hand corner at (-100, -100). This places the center of the square 
      at the origin (0, 0). Give the square a name so we can easily access it via JavaScript: -->  
      <rect id="mySquare" x="-100" y="-100" width="200" height="200" rx="5" ry="5"
            style=" fill: orange; stroke: black; stroke-width: 3; stroke-dasharray: 10, 5;" />
            
      <!-- Represents the x-axis: -->
      <line x1="-400" y1="0" x2="400" y2="0" style="stroke: black;" /> 
    
      <!-- Represents the y-axis (although up is negative and down is positive): -->  
      <line x1="0" y1="-400" x2="0" y2="400" style="stroke: black;" /> 
                
    </g>
  </svg>
</body>

</html>

Perhaps the most difficult part of this code (in the init function), are the following two lines:


transformObject = svgElement.createSVGTransform(); 
mySquare.transform.baseVal.appendItem(transformObject);

The first line creates a generic transform object. The second line attaches this transform object to the DOM under the mySquare node. This allows the mySquare object to inherit all the methods and properties associated with a transform object. In particular, the setRotate method. After this is done, we can rotate the mySquare square by simply calling its (newly acquired) setRotate method as follows:


mySquare.transform.baseVal.getItem(0).setRotate(mySquare.currentTheta, 0, 0);

As an aside, you can avoid the creation and appending of the transform object by adding a "no-op" transform attribute to the rect element; such as transform="matrix(1 0 0 1 0 0)":


<rect id="mySquare" x="-100" y="-100" width="200" height="200" rx="5" ry="5" 
          transform="matrix(1 0 0 1 0 0)"
          style=" fill: orange; stroke: black; stroke-width: 3; stroke-dasharray: 10, 5;" />

This creates a transform object on the rectangle element, which you can then manipulate without having to first create and append one "on the fly". The complete example follows:

Example 4 - No-op Transform Object

Live link: Example 4


<!DOCTYPE html>
<html>

<head>  
  <title>JavaScript SVG Animation</title>
  <!--  <meta http-equiv="X-UA-Compatible" content="IE=Edge"/> Remove this comment only if you have issues rendering this page on an intranet site. -->
  <style>
    /* CSS here. */
  </style>
  <script>  
    /* CONSTANTS */
    var initialTheta = 0; // The initial rotation angle, in degrees.
    var thetaDelta = 0.3; // The amount to rotate the square every "delay" milliseconds, in degrees.
    var delay = 10; // The delay between animation stills, in milliseconds. Affects animation smoothness.
    var angularLimit = 90; // The maximum number of degrees to rotate the square.
    
    /* 
      Note that it will take the square (angularLimit/thetaDelta)*delay milliseconds to rotate an angularLimit
      number of degrees. For example, (90/0.3)*10 = 3000 ms or 3 seconds to rotate the square 90 degrees.
    */
    
    /* GLOBALS */
    var timer; // Contains the setInterval() object, used to stop animation.
    
    function init()
    /*
      Assumes that this function is called after the page loads.
    */
    {
      mySquare.currentTheta = initialTheta; // The initial rotation angle to use when the animation starts.
      timer = setInterval(doAnim, delay); // Call the doAnim() function every "delay" milliseconds until "timer" is cleared.     
    }
    
    function doAnim()
    /*
      This function is called by setInterval() every "delay" milliseconds.
    */
    { 
      if (mySquare.currentTheta > angularLimit)
      {
        clearInterval(timer);
        return;
      }
      
      mySquare.transform.baseVal.getItem(0).setRotate(mySquare.currentTheta, 0, 0); // Rotate the square about the point (0, 0), which is now at the center of the SVG viewport. Assumes a no-op transform attribute has been applied to the mySquare element, such as transform="matrix(1 0 0 1 0 0)".
      mySquare.currentTheta += thetaDelta; // Increase the angle that the square will be rotated to, by a small amount.      
    }
  </script>  
</head>

<body onload="init(); doAnim();"> <!-- init() sets up the animation, doAnim() actually does the animation. -->
  <svg width="800px" height="800px" viewBox="0 0 800 800">

    <g transform="translate(400, 400)"> <!-- Create a Cartesian coordinate system (with the y-axis flipped) for the animated square. That is, place the origin at the center of the 800 x 800 SVG viewport: -->
  
      <!-- A 200 x 200 square with the upper left-hand corner at (-100, -100). This places the center of the square 
      at the origin (0, 0). Note that the no-op transform attribute is necessary to generate a transform object 
      such that the setRotate() method can be utilized in the doAnim() function: -->  
      <rect id="mySquare" x="-100" y="-100" width="200" height="200" rx="5" ry="5" 
            transform="matrix(1 0 0 1 0 0)"
            style=" fill: orange; stroke: black; stroke-width: 3; stroke-dasharray: 10, 5;" />

      <!-- Represents the x-axis: -->
      <line x1="-400" y1="0" x2="400" y2="0" style="stroke: black;" /> 
    
      <!-- Represents the y-axis (although up is negative and down is positive): -->  
      <line x1="0" y1="-400" x2="0" y2="400" style="stroke: black;" /> 
            
    </g>
  </svg>
</body>

</html>

In Example 4, we use an identity matrix as our "no-op" transform attribute to generate a transform object. We could have just as easily used transform=”rotate(0)” instead.

One advantage of Example 3 over Example 4 is that you don’t have to remember to add a "no-op" attribute to the rectangle element.

Now that we have the two scripting styles under our belts, we can move on to more interesting animations. The following example attempts to model rotating gears, circular friction gears in this case. Imagine two bicycle wheels rotating against one another. The rubber’s high coefficient of friction ensures that when one wheel rotates, so will the other. Here is an idealized version of this circular friction gear system:

Two friction gears

The code to generate this image is shown in the next example:

Example 5 - Two Gears

Live link: Example 5


<!DOCTYPE html>
<html>

<head>  
  <title>Two Animated Gears</title>
  <!--  <meta http-equiv="X-UA-Compatible" content="IE=Edge"/> Remove this comment only if you have issues rendering this page on an intranet site. -->
  <style>
    /* CSS here. */
  </style>  
  <script>  
    /* CONSTANTS */
    var initialTheta = 0; // The initial rotation angle, in degrees.
    var currentTheta = initialTheta; // The initial rotation angle to use when the animation starts.    
    var thetaDelta = 0.3; // The amount to rotate the gears every "delay" milliseconds, in degrees.
    var delay = 10; // The delay between animation stills, in milliseconds. Affects animation smoothness.
    var angularLimit = 360; // The maximum number of degrees to rotate the gears.
    
    /* 
      Note that it will take the gears (angularLimit/thetaDelta)*delay milliseconds to rotate an angularLimit
      number of degrees. For example, (90/0.3)*10 = 3000 ms (or 3 seconds) to rotate the gears 90 degrees.
    */
    
    /* GLOBALS */
    var timer; // Contains the setInterval() object, used to stop the animation.  
    
    function init()
    /*
      Assumes that this function is called after the page loads.
    */
    {
      var transformObject = svgElement.createSVGTransform(); // Create a generic SVG transform object so as to gain access to its methods and properties, such as setRotate().;
      
      gear0.transform.baseVal.appendItem(transformObject); // Append the transform object to gear0, now the gear0 object has inherited all the transform object's goodness.
      gear1.transform.baseVal.appendItem(transformObject); // Append the same generic transform object to gear1 - we just want gear1 to inherit all of it's goodness.    
    }
    
    function startAnim()
    {   
      if ( !startButton.startButtonClicked ) // Don't allow multiple instance of the function specified by setInterval to be invoked by the browser. Note that button.startButtonClicked will be undefined on first use, which is effectively the same as false.
      {   
        /* Only do the following once per animation: */
        timer = setInterval(doAnim, delay); // Call the doAnim() function every "delay" milliseconds until "timer" is cleared.     
        startButton.startButtonClicked = true; // A custom property is attached to the button object to track whether the button has been clicked or not.  
      }
    }
    
    function doAnim()
    /*
      This function is called by setInterval() every "delay" milliseconds.
    */
    {       
      if (currentTheta > angularLimit)
      {
        clearInterval(timer); // Instruct the browser to stop calling the function indicated by setInterval();
        startButton.startButtonClicked = false; // Let the user run the animation again if they choose.
        currentTheta = initialTheta; // If we let the user run the animation multiple times, be sure to set currentTheta back to an appropriate value.
        return; // We have completed our animation, time to quit.
      }
      
      gear0.transform.baseVal.getItem(0).setRotate(currentTheta, -150, 0); // Rotate the 0th gear about the point (-150, 0).
      gear1.transform.baseVal.getItem(0).setRotate(-currentTheta, 150, 0); // Rotate the 1st gear, note the minus sign on currentTheta, this rotates the gear in the opposite direction.
      // gear0.setAttribute("transform", "rotate(" + currentTheta + ", -150, 0)"); // More cross-browser friendly, slightly less performant. Note that you don't technically need to append a transform object to each gear object, in init(), when using this line.
      // gear1.setAttribute("transform", "rotate(" + -currentTheta + ", 150, 0)"); // More cross-browser friendly, slightly less performant. Note that you don't technically need to append a transform object to each gear object, in init(), when using this line.
      currentTheta += thetaDelta; // Place this line here so that the gears are not over rotated on the last call to doAnim().
    }
  </script>  
</head>

<body onload="init();"> <!-- init() sets up for the pending animation. -->
  <div align="center"> <!-- An inexpensive way to center everything. -->
    <div style=" margin-bottom: 8px;">
      <button id="startButton" type="button" onclick="startAnim();">
        Start Animation
      </button> 
    </div> 
    <svg id="svgElement" width="800px" height="800px" viewBox="0 0 800 800"> <!-- Give the svg element a name so that we can easily access it via JavaScript. -->
      <rect x="0" y="0" width="100%" height="100%" rx="16" ry="16" 
            style="fill: none; stroke: black; stroke-dasharray: 10 5;" />
  
      <defs> <!-- Do not render the gear template, just define it. -->
        <g id="gearTemplate"> <!-- Give this group of graphic elements a name so that it can be "called" from the <use> element. -->
          <circle cx="0" cy="0" r="150" style="stroke: black;" />
          <line x1="0" y1="-150" x2="0" y2="150" style="stroke: white;"/> <!-- From top to bottom, draw the vertical wheel "spoke". -->        
          <line x1="-150" y1="0" x2="0" y2="0" style="stroke: white;"/> <!-- Draw left half of the horizontal "spoke". -->
          <line x1="0" y1="0" x2="150" y2="0" style="stroke: darkGreen;"/> <!-- Draw right half of the horizontal "spoke". -->
        </g>
      </defs>

      <g transform="translate(400, 400)"> <!-- Create a Cartesian coordinate system (with the y-axis flipped) for the animated gears. That is, place the origin at the center of the 800 x 800 SVG viewport: -->
        <use id="gear0" x="-150" y="0" xlink:href="#gearTemplate" style="fill: orange;" /> <!-- Use the previously defined gear template and position it appropriately. -->
        <use id="gear1" x="150" y="0" xlink:href="#gearTemplate" style="fill: mediumPurple;" /> <!-- Same as the previous line but give this circle a different color. -->                
      </g>
    </svg>
  </div>
</body>

</html>

In Example 5, we animated two graphic objects and added a Start Animation button, which caused quite a bit of code refactoring relative to the rotating square in Example 4. In particular:

  • Instead of creating SVG markup for two separate "gears", we created a gear template that we can reuse as many times as we want to by using the use element.
  • To simplify things, we made currentTheta a global variable so that its value can be applied to both gears.
  • A new function, startAnim, was introduced. Instead of letting the onload page event set up the setInterval call (by using init), we now do this when the Start Animation button is clicked.
  • If the user clicked the Start Animation button multiple times, this would (without protection), invoke multiple instances of doAnim, which makes the animation appear to run much faster than it should. To stop this undesirable behavior, we append the custom property startButtonClicked to the button object and set it to true when the button is first clicked.
  • In order to allow the user to restart the animation (after it completes), the following two lines were added to doAnim:

    
    startButton.startButtonClicked = false; 
    currentTheta = initialTheta; 
    
    

    These lines are executed only when the animation completes (that is, when currentTheta is greater than angularLimit).

One of the issues with Examples 5 is that for each gear, you must individually call its setRotate method as well as remember where each gear’s center should be. This can become tedious if you had many such gears. One solution to this problem is to use an array of gears, as shown in Example 6 (Because of its length, the example code is not shown in this document. Use Windows Internet Explorer's View source feature to view the live example instead). A screenshot of the example follows:

Example 6 - Seventeen Gears

Live link: Example 6

Seventeen super cool friction gears

In the code for Example 6, you can see that it animates 17 gears. As opposed to typing out 17 use elements, all with unique color and (x, y) value information (as was done in Example 5 for two gears), we programmatically build an array of gears containing this information, as well as some other useful information:

  • The radius of each gear (so that, among other things, we can calculate an appropriate rotational speed for the gear).
  • The current angular position (in degrees) of each gear (that is, currentAngle).
  • The name of each gear, and so on.

Additionally, we use the gear array to programmatically append appropriate elements to the DOM such that the gears are rendered on screen. That is, the JavaScript code appends the gear elements to the following “coordinateFrame” g element:


<g id="coordinateFrame" transform="translate(400, 400)"> 
  <!-- Gear <g> elements will be appended here via JavaScript. -->
</g>

The largest gear (“#0” above) is the drive gear. The drive gear rotates all the remaining gears, which are thus idler gears. Because there’s a single drive gear, we set its rotational speed (constants.driveGearSpeed = 0.3) and calculate the required speeds of the other gears based on it, as follows:


gears[i].currentAngle += (gears[i].clockwise * constants.driveGearSpeed * (gears[constants.driveGearIndex].r/gears[i].r));

First, each gear tracks its own angular position via its gears[i].currentAngle property. The direction a gear should rotate is determined by gears[i].clockwise (which is either 1 or -1). A given gear’s current angular position is simply the drive gear’s angular speed multiplied by the drive gear’s radius, divided by the current gear’s radius. Be aware that in the case of the drive gear itself, gears[constants.driveGearIndex].r/gears[i].r is 1, as it should be.

The other item to point out is the use of the custom button property startButtonClicked – this allows the button object to track its own current state – clicked or not.

If you have additional questions about Example 6, review the comments within the sample itself – every important line is described.

The next example extends Example 6 by replacing the single Start Animation button with additional user interface (UI) elements.

Example 7 - Seventeen Gears with Extended UI

Live link: Example 7

This example extends Example 6 by adding the following UI buttons:

Image of added UI buttons

Unsurprisingly, the Start, Pause, and Reset buttons start, pause and reset the animation; whereas the “+” and “-” buttons increase and decrease the speed of the animation. This new UI necessitates a number of code changes, including:

  • There is no longer a need for an angular limit – we let the animation run until the user clicks the Pause or Reset button. To avoid potential variable overflow, we use:
    
    if (gears[i].currentAngle >= 360)
      gears[i].currentAngle -= 360;
    
    

    This keeps each gear’s currentAngle value small without affecting the meaning of currentAngle. That is, a circle rotated 362 degrees looks the same as a circle rotated 2 degrees (for example, 362 – 360 = 2 degrees).

Example 8 - Seventeen Gears with Audio

Live link: Example 8

Using the HTML5 audio element, this example extends the previous example by adding a sound effect that linearly increases or decreases in tempo as the “+” and “-“ buttons are clicked. And given the possibly annoying nature of any sound effect, we’ve also added a convenient Sound Off button:

Image of added UI buttons

The Example 8 audio functions are relatively straightforward and well commented. The only function possibly needing additional explanation is calculatePlaybackRate, which returns an appropriate audio file playback rate given the current rotational speed of the drive gear. To help explain how this is done, consider the following graph:

Drive gear to audio playback rate graph

The x-axis represents the current rotational speed of the drive gear (which can be positive or negative). The y-axis represents an appropriate audio file playback rate (which can only be positive). We know that when the drive gear speed is zero, the audio playback rate should also be zero (that is, no sound). And from our initializing constants, we know that when the drive gear speed is constants.initialDriveGearSpeed, the audio playback rate is constants.initialPlaybackRate. We now have two points: (0, 0) and (constants.initialDriveGearSpeed, constants.initialPlaybackRate). Because two points define a line (and because we want a linear response), we can easily derive the equation used in calculatePlaybackRate by calculating the slope of the line (m) and multiplying it with the absolute value of the current drive gear speed to obtain the correct audio file playback rate (See the comments in calculatePlaybackRate for more information.).

The next logical step might be to add additional gears to Example 8. Because the example’s code uses an array of gear objects, one need only tack on appropriately sized and positioned gears to the array to increase the total number of gears in the animation. This is left as an exercise to the reader and should significantly help your understanding of the techniques presented in this topic.

Related topics

Intermediate SVG Animation
Advanced SVG Animation
HTML5 Graphics
Scalable Vector Graphics (SVG)

 

 

Build date: 5/2/2013

Did you find this helpful?
(1500 characters remaining)

Community Additions

ADD
© 2013 Microsoft. All rights reserved.