24.圆周运动

形状不一定始终沿着直线运动。如果你需要的动画效果是沿着圆周运动,例如,沿着圆形轨道运行(如图1所示)该如何实现呢?这是完全可以实现的,并且不需要使用太多代码,这里需要使用三角函数的相关知识,可能需要你稍微动一下脑筋。

图1
图1 使形状动画沿着圆形轨道运动

概念非常简单:将一个形状放在圆周的边缘处(它的周长上),以圆周的任意位置作为起点。但为了简单起见,可以将形状放在周长上角度为0弧度的位置,该位置位于右手边(如图1所示)。在每次动画循环中,只需要增加位于圆周上的形状的角度,就可以使形状沿着圆周运动。这非常简单,接下来我们具体讨论如何实现。

1.三角函数

需要解决的问题是:如何计算位于圆周上的形状的(x,y)坐标值(如图2所示)。这听上去也许很深奥,但需要解决的问题其实很简单。当然,只有用正确的方式来考虑需要解决的问题,才会觉得它容易。

图2
图2 确定圆周上某个位置对应的坐标值

在解决问题之前,首先需要知道圆的实际大小。可以选择任意大小的圆周,毕竟,这里只是示例,所以实际大小并不重要。重要的是可以通过半径(从圆心到圆周的长度)来描述圆的大小。如果画出运动轨道所在圆周的半径,那么你会发现形状移动的角度遵循一种有趣的模式(如图3所示)。

图3
图3 画出半径,突出了一种有趣的模式

如果你认真地看或者稍微思考一下,也许就会发现这种模式。如果幸运的话,你会发现三角形的边存在一些规律。如果没有发现规律,也不要紧。如果你是第一次接触这种问题,那么稍微发挥一下想象力就可以了。图4中有这里要讨论的三角形。

图4
图4 半径是圆周内直角三角形的一条边

圆周中包含了一个三角形。但它有何用处呢?这个三角形能够提供一些准确的信息,帮助你计算形状沿圆周移动到新位置处的(x,y)坐标值。更具体地说,现在得到了一个三角形和两个角度(沿圆周转动的角度和三角形的90度直角),接下来可以构造一些基本三角形来计算你需要的值。这也体现了数学的重要作用。但是,在真正解决问题之前,我还要简要解释一下三角函数的原理。

三角函数的基本要点是:如果已知一个三角形的一个角是90度,并且已知另外一个角,那么就可以计算三角形的边长之间的比值。然后,可以通过该比值来计算边的长度,边的长度单位是任意的,本示例中边的单位是像素。因此,你需要知道三角形的哪条边是需要计算的长度,因为它们分别对应着不同的三角函数规则。这三条边分别是斜边(最长的边)、邻边(与除直角以外的已知角相邻的边)和对边(与已知角相对的边)。图5详细标注了这些边。

图5
图5 描述直角三角形的三条边

要计算边之间的比值,需要用3种三角函数:正弦函数(sin)、余弦函数(cos)或正切函数(tan)。正弦函数是对边与斜边的比值,余孩函数是邻边与斜边的比值,正切函数是对边与邻边的比值(如图6所示)。你也许听过把这些函数叫做SOH-CAH-TOA,其实这就是代表正弦-对边-斜边、余弦-邻边-斜边、正切-对边-邻边。通过把三角形中的已知角代入正确的函数,可以计算出所需的比值来。

图7
图6 SOH-CAH-TOA公式

在此,我们需要知道三角形的邻边和对边的长度,它们分别代表x和y的位置(如图7所示)。要计算这些边的长度,首先需要在对应的三角函数中通过已知角计算比值。在JavaScript中,可以使用Math对象来计算这些比值:

var angle = 45; 
var adjRatio = Math.cos(angle*(Math.PI/180)); // CAH 
var oppRatio = Math.sin(angle*(Math.PI/180)); // SOH 
图7
图7 通过三角函数计算三角形对应边的比值

你会注意到,Math对象的cos和sin方法中执行了一些简单的计算过程。这种计算是为了将角从角度转换为弧度,因为JavaScript使用的单位是弧度。如果你在开始就使用弧度制,就不需要做任何转换了。

得到这些比值仅仅完成了一半的工作量。另外一半工作才是最终我们需要得到的答案,将这些比值与斜边(因为它是半径,所以长度已知)的长度相比较,如图8所示。最终的答案可以由半径乘以该比值得到,即:

var radius = 50; 
var x = radius * adjRatio; 
var y = radius * oppRatio; 
图8
图8 通过每条边的比值计算坐标值

2.综合运用

既然你能够计算位于圆周上某个角度的形状对应的(x,y)坐标值,那么把这些结果综合应用于当前的示例就非常简单了。第一步是更新Shape类,并向其中添加几个新属性:

var Shape = function(x, y, width, height) { 
        this.x = x;        this.y = y; 
        this.width = width; 
        this.height = height; 
 
        this.radius = Math.random()*30; 
        this.angle = 0; 
}; 

这两个属性用于设置起始角度和计算圆周的随机半径(介于0~30之间)。倒数第二步是使用以下代码替换动画循环中的现有代码,从而更新形状:

var x = tmpShape.x+(tmpShape.radius*Math.cos(tmpShape.angle*(Math.PI/180))); 
var y = tmpShape.y+(tmpShape.radius*Math.sin(tmpShape.angle*(Math.PI/180))); 
 
tmpShape.angle += 5; 
if (tmpShape.angle > 360) { 
        tmpShape.angle = 0; 
};

前两行代码没有什么新内容,它们分别用于计算位于圆周上当前角度的形状所对应的x和y值,其中圆周是通过半径来定义的。这里的x和y值能够提供坐标值(假设圆周中心的坐标为(0,0)),因此,当将x和y值添加到形状中对应的点(x,y)时,就可以把形状移动到正确的位置。注意,形状对象中定义的点(x,y)现在引用的是圆周的中心——形状围绕它旋转的点,而不是形状的起点。最后几行代码用于在每个动画循环中增加角的度数,如果角度超过360度(一个完整的圆),则将角度重新设置为0度。

最后,将新的x和y变量添加到fillRect方法中:

context.fillRect(x, y, tmpShape.width, tmpShape.height); 

如果一切运行正常,就可以选择不同的形状,让它们沿着不同的圆周运动(如图9所示)。

Start
Stop
图9 选择不同的形状围绕圆形轨道运动

本节可能有些难度,以下是完整的代码供你参考。

var canvas = $("#myCanvas"); 
var context = canvas.get(0).getContext("2d"); 
 
var canvasWidth = canvas.width(); 
var canvasHeight = canvas.height(); 
 
var playAnimation = true; 
 
var startButton = $("#startAnimation"); 
var stopButton = $("#stopAnimation"); 
 
startButton.hide(); 
startButton.click(function() { 
        $(this).hide(); 
        stopButton.show(); 
 
        playAnimation = true; 
        animate(); 
}); 
 
stopButton.click(function() { 
        $(this).hide(); 
        startButton.show(); 
        playAnimation = false; 
}); 
var Shape = function(x, y, width, height) { 
        this.x = x; 
        this.y = y; 
        this.width = width; 
        this.height = height; 
        this.radius = Math.random()*30; 
        this.angle = 0; 
}; 
var shapes = new Array(); 
for (var i = 0; i < 10; i++) { 
        var x = Math.random()*250; 
        var y = Math.random()*250; 
        var width = height = Math.random()*30; 
        shapes.push(new Shape(x, y, width, height)); 
}; 
function animate() { 
        context.clearRect(0, 0, canvasWidth, canvasHeight); 
        var shapesLength = shapes.length; 
        for (var i = 0; i < shapesLength; i++) { 
                var tmpShape = shapes[i]; 
                var x = 
tmpShape.x+(tmpShape.radius*Math.cos(tmpShape.angle*(Math.PI/180))); 
                var y = 
tmpShape.y+(tmpShape.radius*Math.sin(tmpShape.angle*(Math.PI/180))); 
                tmpShape.angle += 5; 
                if (tmpShape.angle > 360) { 
                        tmpShape.angle = 0; 
                }; 
                context.fillRect(x, y, tmpShape.width, tmpShape.height); 
        }; 
        if (playAnimation) { 
                setTimeout(animate, 33); 
        }; 
}; 
animate();
文件下载(已下载 2784 次)

发布时间:2013/2/25 上午11:06:08  阅读次数:8104

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号