基本 SVG 动画

来源:互联网 发布:浴缸 知乎 编辑:程序博客网 时间:2024/04/30 13:59

转自:http://msdn.microsoft.com/zh-cn/library/gg193979#example7

本主题将介绍基本 SVG 动画,它是学习中级 SVG 动画之前必须掌握的内容。此主题假定你基本了解 HTML 和 JavaScript。若要完全理解此主题所述的内容,请计划花 1 小时左右的时间来学习。

注意  要查看本主题中包含的示例,必须使用一个支持SVG 元素的浏览器,如 Windows Internet Explorer 9 或更高版本。

  • 示例 1 - 基本的声明性 (SMIL) 动画
  • 示例 2 - 基本 JavaScript 动画
  • 示例 3 - SVG DOM 脚本
  • 示例 4 - No-op 转换对象
  • 示例 5 - 两个摩擦轮
  • 示例 6 - 17 个传动轮
  • 示例 7 - 带有扩展的 UI 的 17 个传动轮
  • 示例 8 - 带有音频的 17 个传动轮
  • 相关主题

尽管在 Windows Internet Explorer 中不支持基本动画(如下所述),但在使用 SVG 的声明性动画构造 (SMIL) 时,基本动画就非常简单。例如,以下代码会以五秒的时间为间隔将一个方块旋转 90 度:

示例 1 - 基本的声明性 (SMIL) 动画

活动链接:示例 1 (本SMIL 示例将不会在 Internet Explorer 中运行)

XML
<?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”,如下面所示:

    HTML
    <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 基础的作者 David Eisenberg 的话说:"如果选择使用脚本执行动画,那么你将拥有脚本提供的所有交互功能;你可让动画依赖于鼠标位置或涉及多个变量的复杂条件。"

也就是说,利用基于脚本的动画,无论是简单还是复杂的动画,你都可能可以实现。因为这一点以及其他原因(如 CSS 动画),Internet Explorer 不支持声明性动画。不可否认的是,对于基于脚本的动画,你可能还有更多相关的工作要做,但是一旦掌握了这些脚本技术,你就可以实现使用纯声明性动画技术不可能实现的动画。以下示例(示例 1 的 JavaScript 版本(用 HTML5 编写))演示了这些技术中的部分技术:

示例 2 - 基本 JavaScript 动画

活动链接: 示例 2

HTML
<!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 标头,从而确保你在最新的标准模式中运行(如果在 Intranet 上进行开发)。

如果你了解传统“卡通”动画的基本原理,那么基于脚本的动画实际上就相对简单了。你可能知道,动画只不过是一系列静止的图像,其中的每个图像不断变化,它们会一个接一个快速地连续显示:

动画静态图像

如果这六个图像足够快地连续显示,人眼将会看到一个跳动的球:

跳动的球动画 GIF 文件

在此例中,跳动的球动画通过重复显示六个图像(每个图像会显示 100 毫秒,然后移动到下一个图像)产生。基于脚本的动画使用了相同的概念。在示例 2 的代码中,我们只调用了一个函数,该函数会不断地每隔几毫秒更改一个图像。尤其是,我们用requestAnimationFrame 来告诉浏览器 doAnim 大约每 16.7 毫秒(也就是每秒 60 帧或 FPS)调用一个函数。每次调用doAnim 函数时,它都会将方块旋转一小段弧度。因为每几毫秒调用一次 doAnim,方块看上去平稳地旋转。当方块旋转angularLimit 数量的角度(示例中为 90°)后,我们会通过调用 doAnim 来指示浏览器停止调用cancelAnimationFrame,然后动画就会停止(有关详细信息,请参见代码注释)。

在介绍更复杂的示例前,有一个重要的注意事项需要指出,那就是 JavaScript 编码的两种样式:

  • DOM L2 脚本和
  • SVG DOM 脚本

DOM L2 脚本属于“传统”的脚本,其特征是通过构建“值字符串”来设置各个项,如下所示:

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

SVG DOM 脚本的特征是缺少这种“值字符串”,通常以数字形式设置元素值,如下所述:

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

这两种方法的效果完全相同。唯一的细微差别是,setRotate 方法需要两个值来指示对象旋转的中心点,本例中的中心点为 (0, 0)。SVG DOM 脚本样式的优点是,你不必构建“值字符串”,而且性能优于 DOM L2 脚本。

下面的示例是示例 2 的 SVG DOM 脚本版本:

示例 3 - SVG DOM 脚本

活动链接: 示例 3

HTML
<!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 最难的部分可能是以下两行:

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

第一行创建一个一般的转换对象。第二行将此转换对象附加到 mySquare 节点。这样,mySquare 对象便可以继承与该转换对象关联的所有方法和属性 - 尤其是setRotate 方法。完成此操作后,我们只需调用 mySquare 方块的(新获取的)setRotate 方法就可以旋转它,如下所示:

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

此外,你还可以通过向 rect 元素添加“no-op”转换特性(如 transform="matrix(1 0 0 1 0 0)")来避免创建和附加转换对象:

JavaScript
<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

JavaScript
<!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

HTML
<!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 中,我们为两个图形对象创建了动画,并添加了“Start Animation”(启动动画)按钮,这导致相对于示例 4 中的旋转方块,需要重构很多代码。具体而言:

  • 我们使用 use 元素创建了一个可无限次使用的传动轮模板,而不是为两个“传动轮”各创建一个 SVG 标记。
  • 为了简化任务,我们将 currentTheta 设为全局变量,从而使它的值能应用于两个传动轮。
  • 引入了一个新函数 startAnim。单击“Start Animation”(启动动画)按钮来调用该函数,该函数通过requestAnimationFrame 启动动画循环。
  • 如果用户多次单击“Start Animation”(启动动画)按钮,则会(没有保护)调用多个 doAnim 实例,从而使动画的运行速度超过预期。为停止此不需要的行为,我们将自定义属性 startButtonClicked 附加到按钮对象,并在首次单击按钮时将其设置为true
  • 为了让用户可以重新启动动画(在动画完成之后),我们向 doAnim 添加了两行代码:

    JavaScript
    startButton.startButtonClicked = false; currentTheta = initialTheta; 

    这两行代码仅在动画完成时(即,当 currentTheta 大于 angularLimit 时)执行。

示例 5 存在的问题之一是:对于每个传动轮,你必须单独调用其setRotate 方法,还必须记住每个传动轮的中心所在。如果你有很多这样的传动轮,那么这会很烦人。针对这个问题的一个解决方案是,使用一个传动轮数组,如示例 6 中所示(出于篇幅的原因,本文档中没有显示该示例代码。请使用 Internet Explorer 的“查看源”功能来查看活动示例)。该示例的屏幕快照如下所示:

示例 6 - 17 个传动轮

活动链接: 示例 6

17 个非常酷的摩擦轮

用示例 6 的代码为 17 个传动轮制作动画。 与尝试键入 17 个use 元素(如同在示例 5 中制作两个传动轮那样,每个都有独特的颜色和 (x, y) 值信息)相反,我们以编程方式创建一系列包含此类信息以及其他有用信息的传动轮:

  • 每个传动轮的半径(这样,我们可以计算出传动轮的适当转速)。
  • 每个传动轮的当前角位置(度数)(即 currentAngle)。
  • 每个传动轮的名称等。

此外,我们使用传动轮数组来以编程方式将适当的元素附加到 DOM,从而使传动轮呈现在屏幕上。也就是说,JavaScript 代码会将传动轮元素附加到以下“coordinateFrame”g 元素:

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

最大的传动轮(上面的“#0”)是主传动轮。主传动轮会旋转所有其余的传动轮(从动传动轮)。 因为只有一个主传动轮,所以我们将设置其转速 (constants.driveGearSpeed = 0.3),并根据它来计算其他传动轮所需的转速,如下所示:

HTML
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 的其他问题,请查看示例本身中的注释 – 对每个重要的行都进行了详细描述。

下一示例通过将单个“Start Animation”(启动动画)按钮替换为其他用户界面 (UI) 元素扩展了示例 6。

示例 7 - 带有扩展的 UI 的 17 个传动轮

活动链接: 示例 7

本例通过添加以下 UI 按钮扩展了示例 6:

添加的 UI 按钮的图像

“Start”(启动)、“Pause”(暂停)和“Reset”(重置)按钮用于启动、暂停和重置动画,而“+”和“-”按钮用于提高和降低动画的速度,这不足为奇。新 UI 导致必须更改很多代码,包括:

  • 不再有角度限制的必要性 – 我们让动画运行,直到用户单击“Pause”(暂停)或“Reset”(重置)按钮时为止。为避免潜在的变量溢出,请使用以下 代码:
    JavaScript
    if (gears[i].currentAngle >= 360)  gears[i].currentAngle -= 360;

    这将使每个传动轮的 currentAngle 保持为较小的值,而不会影响currentAngle 的含义。也就是说,旋转了 362 度的圆看起来与旋转了 2 度的圆(例如,362 – 360 = 2 度)一样。

示例 8 - 带有音频的 17 个传动轮

活动链接: 示例 8

利用 HTML5 audio 元素, 本示例可通过添加声音效果来扩展前面的示例,如果单击“+”或“-”按钮,声音效果的节奏将线性地加快或减慢。由于任何声音效果可能会让人厌烦,因此,我们还添加了方便用户的“Sound Off”(关闭声音)按钮:

添加的 UI 按钮的图像

示例 8 音频代码相对简单并且进行了很好的注释。唯一需要额外说明的功能是calculatePlaybackRate,该功能将根据主传动轮当前的转速返回相应的音频文件播放速度。为了帮助说明如何实现此功能,请考虑以下图像:

主传动轮与音频播放速度图形

x-轴表示主传动轮的当前转速(可能为正,也可能为负)。y-轴表示相应的音频文件播放速度(只可能为正)。我们知道,当主传动轮速度为 0 时,音频播放速度也应该为 0(即,没有声音)。从我们的初始化常量可以看出,当主传动轮速度为constants.initialDriveGearSpeed 时,音频播放速度为 constants.initialPlaybackRate。我们现在有两个点:(0,0) 和 (constants.initialDriveGearSpeed, constants.initialPlaybackRate)。因为两点定义一条直线(且因为我们需要一个线性响应),我们可以通过计算出直线(m)的斜率,并将它乘以当前主传动轮速度的绝对值以获取正确的音频文件播放速度,从而轻松地派生出在calculatePlaybackRate 中所使用的公式(请参阅 calculatePlaybackRate 中的注释以获取详细信息)。

合乎逻辑的下一步骤应该是向示例 8 添加其他传动轮。 因为该示例的代码使用一个传动轮对象数组,所以用户只需向该数组中添加大小和位置合适的传动轮就能增加动画中的传动轮的总数。此任务留给读者作为练习,应该会在很大程度上帮助你理解本主题中介绍的技术。

0 0