使用RequestAnimationFrame在Canvas中以特定速度旋转圆形

5

我在JSFiddle中制作了一个快速简单的解决方案,以更好、更快地解释:

var Canvas = document.getElementById("canvas");
var ctx = Canvas.getContext("2d");

var startAngle = (2*Math.PI);
var endAngle = (Math.PI*1.5);
var currentAngle = 0;

var raf = window.mozRequestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    window.oRequestAnimationFrame;

function Update(){
    //Clears
    ctx.clearRect(0,0,Canvas.width,Canvas.height);

    //Drawing
    ctx.beginPath();                  
    ctx.arc(40, 40, 30, startAngle + currentAngle, endAngle + currentAngle, false);

    ctx.strokeStyle = "orange";
    ctx.lineWidth = 11.0;
    ctx.stroke();

    currentAngle += 0.02;
    document.getElementById("angle").innerHTML=currentAngle;
    raf(Update);
}
raf(Update);

http://jsfiddle.net/YoungDeveloper/YVEhE/3/

由于浏览器选择fps帧速率,我想独立于帧速率旋转环。因为现在,如果速度是30fps,它会旋转得更慢,但如果是60fps,则会更快,因为每个调用都会添加其旋转量。
根据我的理解,从几个线程中了解到它与getTime有关,我确实尝试过,但无法完成,我需要在10秒钟内旋转一次。
另一件事是角度,它会不断增加,经过很长时间后,它会崩溃,因为变量最大值将被超过,所以如何使旋转保持平稳?
谢谢阅读!
3个回答

3

一些数学知识可以让您在动画循环中以指定的速度绘制形状。

演示:http://jsfiddle.net/m1erickson/9Z8pG/

声明一个startTime。

    var startTime=Date.now();

声明一次360度旋转的时间长度(10秒 == 10000毫秒)
    var cycleTime=1000*10;  // 1000ms X 10 seconds

在每个动画帧内...
使用模数运算将当前时间分成10000ms周期。
        var elapsed=Date.now()-startTime;
        var elapsedCycle=elapsed%cycleTime;

在每个动画帧中,计算当前时间通过当前周期的百分比。
        var elapsedCyclePercent=elapsedCycle/cycleTime;

当前旋转角度为一个完整圆 (Math.PI*2) 乘以百分比。

        var radianRotation=Math.PI*2 * elapsedCyclePercent;

重新绘制您的对象以适应当前帧的旋转角度:
        ctx.clearRect(0,0,canvas.width,canvas.height);
        ctx.beginPath();                  
        ctx.arc(40, 40, 30, -Math.PI/2, -Math.PI/2+radianRotation, false);
        ctx.strokeStyle = "orange";
        ctx.lineWidth = 11.0;
        ctx.stroke();

谢谢!很高兴看到除了上面提到的解决方案之外还有其他的解决方案。不幸的是,我不能接受所有的答案!达到15个声望之后,我会点赞这个回答。编辑:已经点赞 :) - JohnPeterson

3

使用时间差方法锁定步骤与旧时间和新时间的差异:

演示

首先获取当前时间:

var oldTime = getTime();

/// for convenience later
function getTime() {
    return (new Date()).getTime();
}

然后在你的循环中:

function Update(){

    var newTime = getTime(),       /// get new time
        diff = newTime - oldTime;  /// calc diff between old and new time
    
    oldTime = newTime;             /// update old time
    
    ...
    
    currentAngle += diff * 0.001;  /// use diff to calc angle step

    /// reset angle
    currentAngle %= 2 * Math.PI;

    raf(Update);
}

使用这种方法将会将动画绑定到时间而不是帧率。

更新 一开始我认为使用角度的MOD运算可能无法适用于浮点数,但实际上可以(我不得不再次确认),所以代码已更新。


谢谢,这就可以了,还要感谢您包含了准备好的JSFiddle! - JohnPeterson
@JohnPeterson 没问题,很高兴能帮到你。 - user1693593

1
为了在不同设备上保持一致的行为,您需要自己处理时间,并基于以下好用的公式更新位置/旋转等:position = speed * time;
其次的好处是,在帧率下降的情况下,移动仍然可以保持相同的速度,因此不容易察觉。

这里有一个样例: http://jsfiddle.net/gamealchemist/YVEhE/6/

在 Javascript 中,时间通常以毫秒表示,所以速度将以弧度每毫秒表示。
这个公式可能会让事情更简单:

var turnsPerSecond = 3;
var speed = turnsPerSecond * 2 * Math.PI / 1000; // in radian per millisecond

然后要更新你的旋转,只需在更新函数开始时计算自上一帧以来经过的时间(dt),并执行以下操作:

    currentAngle +=  speed * dt ;

使用%运算符而不是添加常数,可以避免角度溢出或精度丢失(这将在相当长的时间后发生...):

    currentAngle = currentAngle % ( 2 * Math.PI) ;

 function Update() {
    var callTime = perfNow();
    var dt = callTime - lastUpdateTime;
    lastUpdateTime = callTime;

    raf(Update);
    //Clears
    ctx.clearRect(0, 0, Canvas.width, Canvas.height);

    //Drawing
    ctx.beginPath();
    ctx.arc(40, 40, 30, startAngle + currentAngle, endAngle + currentAngle, false);

    ctx.strokeStyle = "orange";
    ctx.lineWidth = 11.0;
    ctx.stroke();

    currentAngle += (speed * dt);
    currentAngle = currentAngle % (2 * Math.PI);
    angleDisplay.innerHTML = currentAngle;
}

非常感谢,当我达到15个声望时,我会回来并点赞。 - JohnPeterson
1
我认为我的解决方案更方便,因为有计算速度的公式。但这并不重要:你已经有了你的解决方案,所以一切都很好! :-) - GameAlchemist
两个答案对我的解决方案都有帮助,但不幸的是我不能接受两个答案。最起码我可以给其中一个答案点赞。 ;) - JohnPeterson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接