基本 SVG 動畫

本主題涵蓋基本 SVG 動畫,這同時也是中級 SVG 動畫的先決條件。假設您已具備基本的 HTML 和 JavaScript 知識。為了充分了解本主題提供的資料,請準備約一小時的時間來研讀。

附註  若要檢視本主題內包含的範例,您必須使用支援 SVG 元素的瀏覽器,例如 Windows Internet Explorer 9 或更新版本。

雖然 Windows Internet Explorer 不支援基本動畫 (如下所述),但是當您使用 SVG 的宣告式動畫建構 ( SMIL) 時,基本動畫是很簡單的。例如,下列程式碼會以 5 秒的時間為間隔將一個正方形旋轉 90 度:

範例 1 - 基本宣告式 (SMIL) 動畫

動態連結: 範例 1 (這個 SMIL 範例無法在 Internet Explorer 中運作)


<?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 -->
  
  <title>Simplest SVG Animation</title>
  <desc>SVG declarative animation is used to rotate a square.</desc>

  <!-- 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. -->
  <g transform="translate(400, 400)"> 
  
    <!-- 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>

上個範例 (與下面所有範例) 都有相應的註解。 但有一些值得注意的資訊:

  • animateTransform 元素是要製成動畫之物件 (即 rect) 的子系,負責處理所有困難工作,而且相對容易使用。

  • 因為正方形的中心點與檢視區原點 (400, 400) 重疊,因此正方形會繞著中心點旋轉 90 度。例如,如果正方形定義為 x=”0” 且 y=”0”,如下所示:

    
    <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;">
    
    

    則正方形的左上角 (相對於中心點) 會繞著原點旋轉 90 度。試試看吧。

雖然宣告式動畫很簡單,但也有一些限制。SVG Essentials 的作者 David Eisenberg 曾經說過: "如果您選擇使用指令碼製作動畫,您將擁有指令碼可用的所有互動功能;您可以製作依存於滑鼠位置或涉及多個變數之複雜條件的動畫。"

也就是說,使用指令碼製作動畫可以很簡單也可以很複雜。因為這一點以及其他原因 (像是 CSS 動畫),Internet Explorer 不支援宣告式動畫。不可否認的是,使用指令碼製作的動畫可能需要較多工作,但是一旦掌握這些指令碼技術,就可以製作出純粹使用宣告式動畫技術無法完成的動畫。下列範例 (是範例 1 的 JavaScript 版 (使用 HTML5)) 說明其中的一些技術:

範例 2 - 基本 JavaScript 動畫

動態連結: 範例 2


<!DOCTYPE html>
<html>

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

<body>
  <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>
  <script>
    "use strict";

    /* CONSTANTS */
    var initialTheta = 0; // The initial rotation angle, in degrees.
    var thetaDelta = 0.3; // The amount to rotate the square about every 16.7 milliseconds, in degrees.
    var angularLimit = 90; // The maximum number of degrees to rotate the square.
    var theSquare = document.getElementById("mySquare");

    theSquare.currentTheta = initialTheta; // The initial rotation angle to use when the animation starts, stored in a custom property.

    var requestAnimationFrameID = requestAnimationFrame(doAnim); // Start the loop.
    function doAnim() {
      if (theSquare.currentTheta > angularLimit) {
        cancelAnimationFrame(requestAnimationFrameID); // The square has rotated enough, instruct the browser to stop calling the doAnim() function.
        return; // No point in continuing, bail 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.
      requestAnimationFrameID = requestAnimationFrame(doAnim); // Call the doAnim() function about 60 times per second (60 FPS), or about once every 16.7 milliseconds until cancelAnimationFrame() is called.
    }
  </script>
</body>
</html>

重要  相對於在 <head> 區塊內包含 <meta http-equiv-"X-UA-Compatible" content="IE9" /><meta http-equiv-"X-UA-Compatible" content="Edge" />,如果您在內部網路中進行開發,您可以使用 IE=Edge 將 Web 程式開發伺服器設定為傳送 X-UA-Compatible HTTP 標頭,以確保您是以最新的標準模式執行。

如果您了解傳統卡通動畫的基本原理,以指令碼製作的動畫實際上是相對簡單的。您可能已經知道,動畫只是一系列靜止的影像,每個影像漸進式地變更,然後一個接一個快速地連續顯示:

動畫靜止

如果這六個影像連續顯示的速度夠快,人的眼睛就會看到一顆彈跳的球:

彈跳球的 GIF 動畫檔案

在這個案例中,彈跳球動畫的產生是透過重複顯示六個影像,每個影像會顯示 100 毫秒然後移到下一個影像。以指令碼製作的動畫運用了相同的概念。在範例 2 的程式碼中,我們只要呼叫一個函式,就可以每隔幾毫秒不斷變更影像。特別是,我們使用 requestAnimationFrame 告知瀏覽器每隔 16.7 毫秒 (也就是大約每秒 60 個畫面或 FPS) 叫用一次 doAnim 函式。每次呼叫 doAnim 函式時,就會小幅度旋轉正方形。由於每隔數毫秒會呼叫一次 doAnim,因此正方形的旋轉看起來非常順暢。當正方形旋轉 angularLimit 度 (這個範例中是 90°) 之後,我們會呼叫 cancelAnimationFrame 告知瀏覽器停止呼叫 doAnim,動畫就會停止 (如需詳細資訊,請參閱程式碼註解)。

在介紹更複雜的範例之前,有兩個 JavaScript 編碼樣式值得注意:

  • DOM L2 指令碼與
  • SVG DOM 指令碼

DOM L2 指令碼是「傳統」指令碼,其特徵是透過建立「值字串」來設定各種項目,如下所示:


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

SVG DOM 指令碼的特徵則是缺少這類「值字串」,並且通常以數值設定元素值,如下所示:


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

這兩個方法執行的工作完全相同。唯一的細微差異在於 setRotate 方法需要兩個值做為旋轉物件的中心點 (在這個案例中,中心點是 (0, 0))。SVG DOM 指令碼樣式的優點是不需建立「值字串」,而且效能優於 DOM L2 指令碼。

下列範例是範例 2 的 SVG DOM 指令碼版本:

範例 3 - SVG DOM 指令碼

動態連結: 範例 3


<!DOCTYPE html>
<html>

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

<body>
  <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>
  <script>
    "use strict";

    /* CONSTANTS */
    var initialTheta = 0; // The initial rotation angle, in degrees.
    var thetaDelta = 0.3; // The amount to rotate the square about every 16.7 milliseconds, in degrees.
    var angularLimit = 90; // The maximum number of degrees to rotate the square.

    /* GLOBALS */
    var requestAnimationFrameID;
    var mySquare = document.getElementById("mySquare");
    var transformObject;

    mySquare.currentTheta = initialTheta; // The initial rotation angle to use when the animation starts, stored in a custom property.
    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.

    requestAnimationFrameID = requestAnimationFrame(doAnim); // Start the animation loop.
    function doAnim() {
      var transformObject;

      if (mySquare.currentTheta > angularLimit) {
        cancelAnimationFrame(requestAnimationFrameID); // Instruct the browser to stop calling requestAnimationFrame()'s callback.
        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().
      requestAnimationFrameID = requestAnimationFrame(doAnim); // Call the doAnim() function about every 60 times per second (i.e., 60 FPS).
    }
  </script>
</body>
</html>

範例 3 最困難的部分可能就是下列兩行:


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

第一行會建立一般轉換物件。第二行將這個轉換物件連結到 mySquare 節點。這樣可讓 mySquare 物件繼承與轉換物件相關的所有方法與屬性,特別是 setRotate 方法。完成這項操作後,我們只要呼叫新取得的 setRotate 方法,就可以旋轉 mySquare 正方形,如下所示:


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

此外,您還可以將 "no-op" 轉換屬性新增到 rect 元素 (例如 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;" />

這樣會在矩形元素上建立一個轉換物件,您就可以操作該轉換物件,而不需要先動態建立和附加轉換物件。下列是完整範例:

範例 4 - No-op 轉換物件

動態連結: 範例 4


<!DOCTYPE html>
<html>

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

<body>
  <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>
  <script>
    "use strict";

    /* CONSTANTS */
    var initialTheta = 0; // The initial rotation angle, in degrees.
    var thetaDelta = 0.3; // The amount to rotate the square about every 16.7 milliseconds, in degrees.
    var angularLimit = 90; // The maximum number of degrees to rotate the square.

    /* GLOBALS */
    var mySquare = document.getElementById("mySquare");
    var requestAnimationFrameID;

    mySquare.currentTheta = initialTheta; // The initial rotation angle to use when the animation starts.
    requestAnimationFrameID = requestAnimationFrame(doAnim); // Start the animation loop.

    function doAnim() {
      if (mySquare.currentTheta > angularLimit) {
        clearInterval(requestAnimationFrameID);
        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.
      requestAnimationFrameID = requestAnimationFrame(doAnim); // Call the doAnim() function about 60 time per second, or about once every 16.7 milliseconds until cancelRequestAnimation() is called.
    }
  </script>
</body>
</html>

範例 4 中,我們使用單位矩陣做為 "no-op" 轉換屬性以產生轉換物件。我們大可輕鬆使用 transform=”rotate(0)”

範例 3 優於範例 4 的地方在於您不需要記得在矩形元素新增 "no-op" 屬性。

我們已經了解兩種指令碼樣式 (DOM L2 和 SVG DOM),可以繼續了解更有趣的動畫;下列範例嘗試建立一個旋轉的摩擦輪模型。想像兩個橡膠的自行車輪靠著彼此轉動。橡膠的高摩擦係數可確保一個車輪轉動時會帶動另一個車輪。下列是這個圓形摩擦輪系統的理想版本:

兩個摩擦輪

下個範例會說明產生這個影像的程式碼:

範例 5 - 兩個摩擦輪

動態連結: 範例 5


<!DOCTYPE html>
<html>

<head>  
  <title>Two 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="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>
  <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 = 360; // 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');
    var gear1 = document.getElementById('gear1');

    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 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 full animation: */
        startButton.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 doAnim() {
      if (currentTheta > angularLimit) {
        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.
        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).
      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().
      requestAnimationFrameID = requestAnimationFrame(doAnim); // Call the doAnim() function about every 16.7 milliseconds (i.e., about 60 frames per second).
    }
  </script>
</body>
</html>

範例 5 中,我們將兩個圖形物件製成動畫並新增一個 [開始動畫] 按鈕,對範例 4 中的旋轉正方形而言,這造成了相當數量的程式碼重構。特別的是:

  • 我們使用 use 元素建立一個可以多次重複使用的傳動輪範本,而不是為兩個傳動輪個別建立 SVG 標記。
  • 為求簡化,我們將 currentTheta 設為全域變數,這樣就可以將它的值套用到兩個傳送輪。
  • 引進新的函式 startAnim。按一下 [開始動畫] 按鈕叫用這個函式,透過 requestAnimationFrame 啟動動畫迴圈。
  • 如果使用者重複按很多次 [開始動畫] 按鈕,這樣會叫用多個 doAnim 執行個體 (沒有防護機制),讓動畫執行的速度比預期快很多。為停止這項不必要的行為,我們將自訂屬性 startButtonClicked 附加到按鈕物件,並設定第一次按下按鈕時為 true
  • 為了讓使用者可以重新啟動動畫 (在動畫完成後),會在 doAnim 新增下列兩行:

    
    startButton.startButtonClicked = false; 
    currentTheta = initialTheta; 
    
    

    只有在動畫完成時 (也就是 currentTheta 大於 angularLimit 時) 才會執行這些程式行。

範例 5 的其中一個問題是您必須針對每個傳動輪個別呼叫 setRotate 方法,並且還要記住每個傳動輪的中心點位置。如果這種傳動輪的數量很多就會很麻煩。這個問題的解決方法之一就是使用傳動輪陣列,如範例 6 所示 (因為長度的關係,本文件不會顯示這個範例程式碼。請改用 Internet Explorer 的 [檢視原始檔] 功能來檢視即時範例)。範例的螢幕擷取畫面顯示如下:

範例 6 - 十七個傳動輪

動態連結: 範例 6

十七個非常酷的摩擦輪

範例 6 的程式碼中,可製作 17 個傳動輪的動畫。 相對於輸入 17 個 use 元素,其中包含每個傳動輪的獨特色彩與 (x, y) 值資訊 (如同在範例 5 所做的兩個傳動輪設定),我們以程式設計方式建立包含這項資訊的傳動輪陣列和一些其他有用的資訊:

  • 每個傳動輪的半徑 (以便結合其他參數計算傳動輪適當的轉動速度)。
  • 每個傳動輪目前的角度位置 (以度為單位),亦即 currentAngle
  • 每個傳動輪的名稱等。

此外,我們使用傳動輪陣列以程式設計方式附加適當元素到 DOM,使傳動輪顯示在畫面上。也就是說,JavaScript 程式碼會附加傳動輪元素到下列 “coordinateFrame” g 元素:


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

最大的傳動輪 (上面的 “#0”) 是主傳動輪。主傳動輪會帶動其他所有的傳動輪,也就是中間傳動輪。 因為只有一個主傳動輪,我們會設定主傳動輪的轉動速度 (constants.driveGearSpeed = 0.3),並依據主傳動輪計算其他傳動輪所需的速度,如下所示:


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

首先,每個傳動輪會透過 gears[i].currentAngle 屬性追蹤自己的角度位置。傳動輪轉動的方向由 gears[i].clockwise (1 或 -1) 決定。指定的傳動輪目前角度位置是主傳動輪的角度速度乘以主傳動輪半徑,再除以目前傳動輪的半徑。請注意,如果是主傳動輪本身,gears[constants.driveGearIndex].r/gears[i].r 應為 1。

另一個值得注意的項目是使用自訂按鈕屬性 startButtonClicked,這個屬性可讓按鈕物件追蹤自己的目前狀態 (是否按下)。

如有其他範例 6 相關問題,請檢視範例本身的註解,每個重要的程式行都有完整說明。

下一個範例是範例 6 的延伸,將單一 [開始動畫] 按鈕更換為其他使用者介面 (UI) 元素。

範例 7 - 十七個傳動輪以及擴充的 UI

動態連結: 範例 7

這個範例是範例 6 的延伸,加入下列 UI 按鈕:

新增的 UI 按鈕影像

很顯然地,[開始]、[暫停] 及 [重設] 按鈕會開始、暫停及重設動畫;而 “+” 與 “-” 按鈕則會增加及減少動畫速度。這個新 UI 必須進行一些程式碼修改,其中包含:

  • 不再需要角度限制。我們讓動畫執行到使用者按一下 [暫停] 或 [重設] 按鈕為止。為避免可能的變數溢位,請使用下列程式碼:
    
    if (gears[i].currentAngle >= 360)
      gears[i].currentAngle -= 360;
    
    

    這可使每個傳動輪維持在最小的 currentAngle 值,卻不影響 currentAngle 的意義。也就是說,旋轉 362 度的圓圈看起來與旋轉 2 度的圓圈相同 (例如,362 – 360 = 2 度)。

範例 8 - 十七個傳動輪以及音訊

動態連結: 範例 8

這個範例使用 HTML5 audio 元素來延伸上個範例,透過按一下 “+” 與 “-“ 按鈕以線性方式增加或減少音效的節拍。由於任何音效都可能讓人感到厭煩,所以我們新增了便利的 [關閉音效] 按鈕:

新增的 UI 按鈕影像

範例 8 音訊程式碼相對簡單,而且註解完整。唯一可能需要額外說明的函式為 calculatePlaybackRate,它會依照主傳動輪目前的轉動速度傳回適當的音訊檔案播放速率。為說明這項動作如何完成,請注意下列圖形:

主傳動輪對音訊播放速率圖形

x 軸代表主傳動輪目前的轉動速度 (可以是正數或負數)。y 軸代表適當的音訊檔案播放速率 (只能是正數)。我們知道當主傳動輪的速度為零時,音訊播放速率也應該是零 (也就是沒有音效)。從我們的初始化常數可知,當主傳動輪速度為 constants.initialDriveGearSpeed 時,音訊播放速率為 constants.initialPlaybackRate。我們現在有兩個點:(0, 0) 與 (constants.initialDriveGearSpeed, constants.initialPlaybackRate)。由於兩個點可以定義一條直線 (而且因為我們需要線性回應),我們可以計算直線的斜率 (m),然後乘以目前主傳動輪速度的絕對值,以得到正確的音訊檔案播放速率 (如需詳細資訊,請參閱 calculatePlaybackRate 中的註解),就可以輕鬆推算出 calculatePlaybackRate 使用的方程式。

下一個合理的步驟應該就是新增其他傳動輪到範例 8。 由於範例的程式碼使用傳動輪物件陣列,因此只要將適當大小與適當定位的傳動輪加到陣列,就能增加動畫中的傳動輪總數。這個問題留給讀者做為練習,應該會在很大程度上幫助您了解本主題中介紹的技術。

相關主題

中級 SVG 動畫
進階 SVG 動畫
HTML5 圖形
Scalable Vector Graphics (SVG)

 

 

顯示:
© 2014 Microsoft