Search code examples
htmlsvgsvg-animate

rotatation of a gear counter-clockwise in svg


I want to have a simple gear and be able to rotate it clockwise, counter-clockwise and pause the animation. My code works fine when I had just these two buttons: clockwise and pause; but after adding counter-clockwise button and its realated functions, nothing works!
Fiddle

    <!DOCTYPE html>
<html>

<head>  
  <title> Animated Gears</title>
  <meta http-equiv="X-UA-Compatible" content="IE=Edge"/> <!--  Remove this line in production. -->
</head>

<body>
  <div align="center"> <!-- An inexpensive way to center everything. -->
    <div style=" margin-bottom: 8px;">
      <button id="clockwise" type="button" onclick="clockwise();">
        clockwise
      </button> 
              <button id="pause" type="button" onclick="pauseAnim();">
        Pause
      </button> 
<button id="counterclockwise" type="button" onclick="counterclockwise();">
        Counterclockwise
      </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: white;"/> <!-- 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. -->

      </g>
    </svg>
  </div>



 <script>

       "use strict";

    /* 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.5; // The amount to rotate the gears every ~16.7 milliseconds or so, in degrees.
    var angularLimit = 1080; // The maximum number of degrees to rotate the gears.

    /* GLOBALS */
    var requestAnimationFrameID;
    var transformObject = svgElement.createSVGTransform(); // Create a generic SVG transform object so as to gain access to its methods and properties, such as setRotate().
    var gear0 = document.getElementById('gear0');

gear0.transform.baseVal.appendItem(transformObject); // Append the transform object to gear0, now the gear0 object has inherited all the transform object's goodness.


     //...........................................     


function clockwise() {

if (!clockwise.startButtonClicked) { // Don't allow multiple instance of the function specified by requestAnimationFrame 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: */

        clockwise.startButtonClicked = true; // A custom property is attached to the button object to track whether the button has been clicked or not.
        requestAnimationFrameID = requestAnimationFrame(doAnim); // Start the animation loop.
      }
    }

     //..............................................
     function counterclockwise() {

if (!counterclockwise.startButtonClicked) { // Don't allow multiple instance of the function specified by requestAnimationFrame 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: */

        counterclockwise.startButtonClicked = true; // A custom property is attached to the button object to track whether the button has been clicked or not.
        requestAnimationFrameID = requestAnimationFrame(doAnim2); // Start the animation loop.
      }
    }



     //........................
    function pauseAnim() {
      cancelAnimationFrame(requestAnimationFrameID); // Stop calling the doAnim() function.

      clockwise.startButtonClicked = false; // Allow
     requestAnimationFrame() to be called if the Start button is clicked again. And disable the "+" or "-" buttons when paused.
     counterclockwise.startButtonClicked = false;

    }

         function doAnim() {
      if (currentTheta > angularLimit) {
        clockwise.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.
        cancelAnimationFrame(requestAnimationFrameID); // Instruct the browser to stop calling requestAnimationFrame()'s callback.
        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).

      currentTheta += thetaDelta; // Place this line here so that the gears are not over rotated on the last call to doAnim().
      requestAnimationFrameID = requestAnimationFrame(doAnim); // Call the doAnim() function about every 16.7 milliseconds (i.e., about 60 frames per second).     
     }
     //.................................................................

     function doAnim2() {
      if (currentTheta > angularLimit) {
        counterclockwise.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.
        cancelAnimationFrame(requestAnimationFrameID); // Instruct the browser to stop calling requestAnimationFrame()'s callback.
        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).

      currentTheta += thetaDelta; // Place this line here so that the gears are not over rotated on the last call to doAnim().
      requestAnimationFrameID = requestAnimationFrame(doAnim2); // Call the doAnim() function about every 16.7 milliseconds (i.e., about 60 frames per second).     
     }



     </script>
</body>
</html>    

Solution

  • Edit 01/19/15

    Now that you posted your try about the counterclockwise function, it appears that you had a problem with comments inside the pause function (requestAnimationFrame() to be called if the Start button is… wasn't commented, leading to a syntax error.)
    Also, I notice that you were applying -currentTheta to the transform, which leads to a gap if you go from 'clockwise' to 'counterclockwise'.


    Well the counterclockwise function is missing in your snippet. So it is very hard to tell you what's wrong.
    But I'll admit it's just the same as clockwise but in the other way :

    var initialTheta = 0;
    var currentTheta = initialTheta;
    var thetaDelta = 0.5;
    var angularLimit = 1080;
    var angularLimitMin = -1080;
    
    var requestAnimationFrameID;
    var transformObject = svgElement.createSVGTransform();
    var gear0 = document.getElementById('gear0');
    
    gear0.transform.baseVal.appendItem(transformObject);
    
    
    function clockwise() {
      if (!clockwise.startButtonClicked) {
        clockwise.startButtonClicked = true;
        requestAnimationFrameID = requestAnimationFrame(doAnim);
      }
    }
    
    
    function counterclockwise() {
      if (!counterclockwise.startButtonClicked) {
        counterclockwise.startButtonClicked = true;
        requestAnimationFrameID = requestAnimationFrame(undoAnim);
      }
    }
    
    
    function pauseAnim() {
      cancelAnimationFrame(requestAnimationFrameID);
      clockwise.startButtonClicked = false;
      counterclockwise.startButtonClicked = false;
    }
    
    function doAnim() {
      if (currentTheta > angularLimit) {
        clockwise.startButtonClicked = false;
        currentTheta = initialTheta;
        cancelAnimationFrame(requestAnimationFrameID);
        return;
      }
    
      gear0.transform.baseVal.getItem(0).setRotate(currentTheta, -150, 0);
      currentTheta += thetaDelta;
      requestAnimationFrameID = requestAnimationFrame(doAnim);
    }
    
    function undoAnim() {
      if (currentTheta < angularLimitMin) {
        counterclockwise.startButtonClicked = false;
        currentTheta = initialTheta;
        cancelAnimationFrame(requestAnimationFrameID);
        return;
      }
      gear0.transform.baseVal.getItem(0).setRotate(currentTheta, -150, 0);
      currentTheta -= thetaDelta;
      requestAnimationFrameID = requestAnimationFrame(undoAnim);
    }
    <div align="center">
      <!-- An inexpensive way to center everything. -->
      <div style=" margin-bottom: 8px;">
        <button id="clockwise" type="button" onclick="clockwise();">clockwise</button>
        <button id="pause" type="button" onclick="pauseAnim();">Pause</button>
        <button id="counterclockwise" type="button" onclick="counterclockwise();">Counterclockwise</button>
      </div>
      <svg id="svgElement" width="800px" height="800px" viewBox="0 0 800 800">
        <rect x="0" y="0" width="100%" height="100%" rx="16" ry="16" style="fill: none; stroke: black; stroke-dasharray: 10, 5;" />
        <defs>
          <g id="gearTemplate">
            <circle cx="0" cy="0" r="150" style="stroke: black;" />
            <line x1="0" y1="-150" x2="0" y2="150" style="stroke: white;" />
            <line x1="-150" y1="0" x2="0" y2="0" style="stroke: white;" />
            <line x1="0" y1="0" x2="150" y2="0" style="stroke: white;" />
          </g>
        </defs>
        <g transform="translate(400, 400)">
          <use id="gear0" x="-150" y="0" xlink:href="#gearTemplate" style="fill: orange;" />
        </g>
      </svg>
    </div>

    And the updated snippet with your comments