此主题紧接基本 SVG 动画主题,将介绍一些中级 SVG 动画技术。若要彻底理解此主题中所述的概念,请计划花 1 小时左右的时间来学习。html
注意 要查看本主题中包含的示例,必须使用一个支持 SVG 元素的浏览器,如 Windows Internet Explorer 9。编程
在基本 SVG 动画中,咱们主要介绍了对象的旋转。在本主题中,咱们主要介绍对象的平移(即空间运动)以及这类平移的最多见结果 - 碰撞。浏览器
为了研究对象平移和碰撞,咱们首先介绍可能最简单的对象 - 圆形。如下示例将在屏幕上移动圆形:服务器
活动连接: 示例 1ide
<!DOCTYPE html> <html> <head> <title>SVG Animation - Circle Translation</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> var timer; // Contains the setInterval() object, which is used to stop the animation. var delay = 16; // Invoke the function specified in setInterval() every "delay" milliseconds. This value affects animation smoothness. /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ function s2d(s) /* The function name "s2d" means "speed to displacement". This function returns the required displacement value for an object traveling at "s" pixels per second. This function assumes the following: * The parameter s is in pixels per second. * "constants.delay" is a valid global constant. * The SVG viewport is set up such that 1 user unit equals 1 pixel. */ { return (s / 1000) * delay; // Given "constants.delay", return the object's displacement such that it will travel at s pixels per second across the screen. } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ function init() { svgElement = document.getElementById("svgElement"); // Required for Mozilla, this line is not necessary for IE9 or Chrome. circle0 = document.getElementById("circle0"); // Required for Mozilla, this line is not necessary IE9 or Chrome. timer = setInterval(doAnim, delay); // Call the doAnim() function every "delay" milliseconds until "timer" is cleared. /* Create custom properties to store the circle's velocity: */ circle0.vx = 150; // Move the circle at a velocity of 50 pixels per second in the x-direction. circle0.vy = 80; // Move the circle at a velocity of 20 pixels per second in the y-direction. } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ function doAnim() { var r = circle0.r.baseVal.value; // The radius of circle0. var boxWidth = svgElement.width.baseVal.value; // The width of the SVG viewport. var boxHeight = svgElement.height.baseVal.value; // The height of the SVG viewport. circle0.cx.baseVal.value += s2d(circle0.vx); // Move the circle in the x-direction by a small amount. circle0.cy.baseVal.value += s2d(circle0.vy); // Move the circle in the y-direction by a small amount. if ( (circle0.cx.baseVal.value >= (boxWidth - r)) || (circle0.cy.baseVal.value >= (boxHeight - r)) ) // Detect if the circle attempts to exit the SVG viewport assuming the ball is moving to the right and down. clearInterval(timer); // The circle has hit the bottom or right wall so instruct the browser to stop calling doAnim(). } </script> </head> <body οnlοad="init();"> <svg id="svgElement" width="800px" height="600px" viewBox="0 0 800 600"> <rect x="0" y="0" width="100%" height="100%" rx="10" ry="10" style="fill: white; stroke: black;" /> <circle id="circle0" cx="40" cy="40" r="40" style="fill: orange; stroke: black; stroke-width: 1;" /> </svg> </body> </html>
要点 与在 <head>
块中包括 <meta http-equiv-"X-UA-Compatible" content="IE-9" />
或 <meta http-equiv-"X-UA-Compatible" content="IE-Edge" />
相反,你可使用 IE=Edge
将 Web 开发服务器配置为发送 X-UA-Compatible HTTP 标头,从而确保你在最新的标准模式中运行(若是你在 Intranet 上进行开发的话)。svg
如以上代码示例中所示,咱们使用 SVG DOM 脚本样式(有关对此样式的讨论,请参阅基本 SVG 动画)。函数
基本概念很是简单 – 每隔 16 毫秒(即,delay
的值),咱们将圆心位置移动一点。例如,在伪代码中,咱们使用:学习
咱们没有对 Δx 的值(即 0.5)和 Δy 的值(即 0.2)进行硬编码,而是经过向圆形元素追加两个新的自定义属性,为圆形指定了一个速度矢量:测试
能够经过图形方式表示此速度矢量 v,以下所示:动画
Figure 1
所以,circle0.vx
是圆形的速度矢量的 x 轴份量(单位为每秒像素数),而 circle0.vy
是速度矢量的 y 轴份量(单位为每秒像素数)。请注意,上面的 xy 坐标系表示原点在屏幕左上角的 SVG 视区。
咱们如今须要一个函数,将速度矢量的一个份量平移到相应的位移以实现动画目的。可经过使用 s2d(v)
函数来完成此操做。例如,若是 v
参数为每秒 50 个像素,且 delay
为 16 毫秒,则经过使用维度分析,获得的位移结果为 (50pixels/s)•(1s/1000ms)•(16ms) = 0.8 像素。
最终,当圆形碰到 SVG 视区的右侧或底部“框壁”时,动画中止。也就是说,咱们须要一个简单形式的碰撞检测:
由于咱们须要肯定圆形的边什么时候碰到壁(相对于圆心),因此咱们必须减去圆的半径,如上面的代码段中所示(即 boxWidth – r
和 boxHeight – r
)。
经过使用上面的碰撞检测技术,下面的示例将演示球(即圆形)弹离壁的轨迹:
活动连接: 示例 2
<!DOCTYPE html> <html> <head> <title>SVG Animation - Circle Translation</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> var timer; // Contains the setInterval() object, used to stop the animation. var delay = 10; // Invoke the function specified in setInterval() every "delay" milliseconds. This value affects animation smoothness. /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ function s2d(s) /* The function name "s2d" means "speed to displacement". This function returns the required displacement value for an object traveling at "s" pixels per second. This function assumes the following: * The parameter s is in pixels per second. * "constants.delay" is a valid global constant. * The SVG viewport is set up such that 1 user unit equals 1 pixel. */ { return (s / 1000) * delay; // Given "constants.delay", return the object's displacement such that it will travel at s pixels per second across the screen. } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ function init() { svgElement = document.getElementById("svgElement"); // Required for Mozilla, this line is not necessary IE9 or Chrome. circle0 = document.getElementById("circle0"); // Required for Mozilla, this line is not necessaryIE9 or Chrome. timer = setInterval(doAnim, delay); // Call the doAnim() function every "delay" milliseconds until "timer" is cleared. /* Create custom properties to store the circle's velocity: */ circle0.vx = 150; // Move the circle at a velocity of 50 pixels per second in the x-direction. circle0.vy = 60; // Move the circle at a velocity of 20 pixels per second in the y-direction. } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ function doAnim() { var r = circle0.r.baseVal.value; // The radius of circle0. var boxWidth = svgElement.width.baseVal.value; // The width of the SVG viewport. var boxHeight = svgElement.height.baseVal.value; // The height of the SVG viewport. circle0.cx.baseVal.value += s2d(circle0.vx); // Move the circle in the x-direction by a small amount. circle0.cy.baseVal.value += s2d(circle0.vy); // Move the circle in the y-direction by a small amount. /* Assumes the circle's velocity is such that it will only hit the right wall: */ if ( circle0.cx.baseVal.value >= (boxWidth - r) ) // Detect if the circle attempts to exit the right side of the SVG viewport. circle0.vx *= -1; // Reverse the direction of the x-component of the ball's velocity vector - this is a right-wall bounce. if ( circle0.cy.baseVal.value >= (boxHeight - r) ) clearInterval(timer); // The circle has hit the bottom wall so instruct the browser to stop calling doAnim(). } </script> </head> <body οnlοad="init();"> <svg id="svgElement" width="800px" height="600px" viewBox="0 0 800 600"> <rect x="0" y="0" width="100%" height="100%" rx="10" ry="10" style="fill: white; stroke: black;" /> <circle id="circle0" cx="40" cy="40" r="40" style="fill: orange; stroke: black; stroke-width: 1;" /> </svg> </body> </html>
球弹离壁的关键概念是矢量反射,如如下简化图形所示:
Figure 2
在图 2 中,右侧黑色虚线表示壁,vin 表示球碰到壁以前的速度矢量,vout 表示球碰到壁以后的速度矢量。你能够看到(在此特定状况下),惟一变化的是向外速度矢量幅度的 x 轴份量的符号。所以,要使球弹离右壁,只需改变球的速度矢量的 x 轴份量的符号便可:
请注意,咱们已经决定在球碰到底壁时中止动画:
上面的示例存在某种人为设定的因素,只有在球最初彻底按正确的方向移动时,代码才会起做用。接下来的示例消除了人为设定的因素。但在你继续以前,再看一下图 2。想像蓝色矢量弹离左壁。应该很明显,依照右壁的状况,你只须要更改速度矢量的 x 轴份量的符号便可得到正确的行为。经过对顶壁和底壁使用此相同参数,能够看出,你只须要更改 y 轴份量的符号便可得到正确的结果。这是在如下示例中使用的逻辑:
活动连接: 示例 3
<!DOCTYPE html> <html> <head> <title>SVG Animation - Circle Translation</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> var timer; // Contains the setInterval() object, used to stop the animation. var delay = 10; // Invoke the function specified in setInterval() every "delay" milliseconds. This value affects animation smoothness. /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ function s2d(s) /* The function name "s2d" means "speed to displacement". This function returns the required displacement value for an object traveling at "s" pixels per second. This function assumes the following: * The parameter s is in pixels per second. * "constants.delay" is a valid global constant. * The SVG viewport is set up such that 1 user unit equals 1 pixel. */ { return (s / 1000) * delay; // Given "constants.delay", return the object's displacement such that it will travel at s pixels per second across the screen. } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ function init() { svgElement = document.getElementById("svgElement"); // Required for Mozilla, this line is not necessary for IE9 or Chrome. circle0 = document.getElementById("circle0"); // Required for Mozilla, this line is not necessary for IE9 or Chrome. timer = setInterval(doAnim, delay); // Call the doAnim() function every "delay" milliseconds until "timer" is cleared. /* Create custom properties to store the circle's velocity: */ circle0.vx = 200; // Move the circle at a velocity of 200 pixels per second in the x-direction. circle0.vy = 80; // Move the circle at a velocity of 80 pixels per second in the y-direction. } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ function verticalWallCollision(r, width) /* Returns true if circl0 has hit (or gone past) the left or the right wall; false otherwise. */ { return ( (circle0.cx.baseVal.value <= r) || (circle0.cx.baseVal.value >= (width - r)) ); } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ function horizontalWallCollision(r, height) /* Returns true if circl0 has hit (or gone past) the top or the bottom wall; false otherwise. */ { return ( (circle0.cy.baseVal.value <= r) || (circle0.cy.baseVal.value >= (height - r)) ); } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ function doAnim() { var r = circle0.r.baseVal.value; // The radius of circle0. var boxWidth = svgElement.width.baseVal.value; // The width of the SVG viewport. var boxHeight = svgElement.height.baseVal.value; // The height of the SVG viewport. circle0.cx.baseVal.value += s2d(circle0.vx); // Move the circle in the x-direction by a small amount. circle0.cy.baseVal.value += s2d(circle0.vy); // Move the circle in the y-direction by a small amount. if ( verticalWallCollision(r, boxWidth) ) circle0.vx *= -1; // Reverse the direction of the x-component of the ball's velocity vector. if ( horizontalWallCollision(r, boxHeight) ) circle0.vy *= -1; // Reverse the direction of the y-component of the ball's velocity vector. } </script> </head> <body οnlοad="init();"> <svg id="svgElement" width="800px" height="600px" viewBox="0 0 800 600"> <rect x="0" y="0" width="100%" height="100%" rx="10" ry="10" style="fill: white; stroke: black;" /> <circle id="circle0" cx="40" cy="40" r="40" style="fill: orange; stroke: black; stroke-width: 1;" /> </svg> </body> </html>
示例 2 - 一面壁弹跳和示例 3 - 四面壁弹跳之间惟一显著的区别在于 verticalWallCollision(r, width)
和 horizontalWallCollision(r, height)
这两个函数。后一个函数仅包含下面一行代码:
使用下图能够轻松理解这行彷佛很神秘的代码:
Figure 3
如图 3 中所示,当球心的 y 坐标大于或等于相对于底壁 r 的距离时,表示球已经与底壁碰撞。此距离简单表示为 height – r。所以,咱们对底壁的测试将变成:
一样,当球心的 y 坐标小于或等于距离 r 时,表示球已经与顶壁碰撞。再次,此距离简单表示为 r – 0 = r,所以对顶壁的测试为:
合并这两个测试将产生上面的返回语句。
活动连接: 示例 4
观看一个球在盒子中来回弹跳能够娱乐几分钟时间。不过,下一步向盒子中添加另外一个球后,会增添一些乐趣。执行此操做要求处理球与球碰撞以及相关数学运算。 为了帮助你开始操做,下面提供了示例 4。请注意,因长度的缘故,没有显示该示例代码,而使用 Windows Internet Explorer 中的View source功能查看关联的代码。为了方便起见,下面显示了示例 4 的屏幕截图:
首先,咱们建立一个对象,该对象表示四个经常使用矢量运算的泛型矢量和函数:
若是理解基本矢量运算,那么这些函数能够直接实现。为了更好地了解矢量及其关联运算,请参阅 Wikipedia 或 Wolfram MathWorld。
请注意,在该示例中,矢量函数包含在标记为“VECTOR FUNCTIONS”的脚本块内,且带有相应的注释。可是,关于这方面要指出的一点是,每一个圆形元素(即球)按以下所示沿本身的速度矢量运动(请参阅 init
函数):
在上面,本地建立了一个新的泛型矢量 gv0
,且该矢量追加到全局 ball0
圆形元素。完成此操做后,球 0 的速度矢量的 x 轴向份量和 y 轴向份量将分别设置为每秒 200 像素和每秒 80 像素。
球与壁碰撞已在示例 3 中描述过,如今剩下球与球碰撞。遗憾的是,关联的数学运算很是复杂。在高级别上,要肯定已碰撞两个球在碰撞后的正确速度矢量,须要进行下面的数学计算:
有关详细信息,请参阅碰撞响应中的“Have Collision, Will Travel”部分。
活动连接: 示例 5
如今,咱们已经介绍了球与壁及球与球的碰撞,咱们能够延伸示例 4,将许多球全都放到一个球形竞技场(而不是盒中)中进行碰撞,一个“球竞技场”。
一样由于长度的缘故,没有显示该示例的代码(使用“查看源”能够查看这些代码)。可是提供了下面的屏幕截图:
要提到的关键代码相关项包括:
circle
元素),并将自定义属性追加到这些元素(如速度矢量对象)。 constants.epsilon
),能够调整每次弹跳所丢失的能量。值 1 指示不该丢失能量,与纯弹性碰撞同样。 示例 6 与示例 5 彻底相同,可是它采用了更加面向对象的方式。
相对于最后两个示例,接下来的逻辑步骤可能包含:
这些扩展留做读者练习之用,应该会在很大程度上帮助你理解本主题中介绍的技术。