使用requestAnimationFrame绘制弧

时间:2013-08-03 20:49:02

标签: javascript jquery canvas requestanimationframe

FIDDLE

我正在做什么:我正在使用设置的间隔从弧线绘制一个cirlce。绘制一个圆后,我正在绘制另一个半径稍微增大的圆圈,如小提琴所示。

我想做什么:我希望实现相同的功能,但应使用 requestAnimationFrame 在1分钟(即60秒绘制一个圆圈)完成一个圆圈。并避免使用setInterval。

我知道RAF是什么,但无法实现它

60秒...... RAF ......圈。 ?? 我的代码:

    //for full code see the fiddle.

    setInterval(function () {
    context.save();
    //context.clearRect(0, 0, 500, 400);
    context.beginPath();

    increase_end_angle = increase_end_angle + 11 / 500;
    dynamic_end_angle = end_angle + increase_end_angle;
    context.arc(x, y, radius, start_angle, dynamic_end_angle, false);

    context.lineWidth = 6;
    context.lineCap = "round";
    context.stroke();
    context.restore();
    if (dynamic_end_angle > 3.5 * Math.PI) { //condition for if circle completion
        increase_end_angle = 0;
        draw(radius + 10); //draw from same origin.
    }
}, 3);

仅针对Ken更新了问题

1)如何将所有内容乘以2(即比例)会使一切变得更清晰。

2)我知道setInterval(anim,120); //这部分......比较......这就是为什么圆圈在60秒内完成的原因?并且我总是怀疑使用setInterval.Reason是它确实提供了一段时间后的混蛋。虽然在这种情况下虽然。我dint希望我的动画停止,这总是发生在使用RAF.But再次raf将是伟大的优化。所以有点困惑,但我猜我会采用setInterval方式。

3)这个问题有点困难,我现在正在研究它。如果我无法做到,我会接受你的建议。它处理一些json,创建多个实例并停止动画时数据停止了。明天我会试试,现在太累了。

谢谢你的答案!正是我想要的。

2 个答案:

答案 0 :(得分:2)

setInterval重复调用函数时,requestAnimationFrame会将其调度为仅调用一次。如果您希望重复调用函数,可以在函数结束时再次调用 requestAnimationFrame

以下是基于您的代码的示例。请注意两次调用 requestAnimationFrame

  1. 在最底部,这是第一次调用 drawFrame
  2. drawFrame 函数的末尾。在 drawFrame 完成其业务后,它会调用 requestAnimationFrame 在下一个动画帧时再次调用自身。
  3. JSFiddle link

    // set up the geometry
    var start_angle = -0.5 * Math.PI;
    var end_angle = 1.5 * Math.PI;
    var arc_end_angle = start_angle;
    var angle_step = 2 * Math.PI / 60;
    var radius = 30;
    
    // set up the canvas
    var canvas = document.getElementById('myCanvas');
    var context = canvas.getContext('2d');
    context.lineWidth = 6;
    context.lineCap = "round";
    
    // the drawFrame function is called up to 60 times per second
    function drawFrame() {
    
        // draw an arc
        context.beginPath();
        context.arc(
            canvas.width / 2,
            canvas.height / 2,
            radius,
            start_angle,
            arc_end_angle,
            false);
        context.stroke();
    
        // update the geometry, for use in the next call to
        // drawFrame
        arc_end_angle += angle_step;
        if (arc_end_angle > end_angle) {
            arc_end_angle = start_angle;
            radius += 10;
        }
    
        // request that drawFrame be called again, for the
        // next animation frame
        requestAnimationFrame(drawFrame);
    }
    
    // request that drawFrame be called once, in the next
    // animation frame
    requestAnimationFrame(drawFrame);
    

    请注意, requestAnimationFrame 并不能保证您的函数每秒精确调用60次。来自MDN documentation(强调我的):

    只要您准备好在屏幕上更新动画,就应该调用此方法。这将请求在浏览器执行下一次重绘之前调用您的动画函数。对于前景标签,该重绘可能会出现最多每秒60次(确切的速率由浏览器决定),但可能会降低到较低的速率标签。

    如果您需要在一秒钟内完成弧线,请确保根据传球时间计算弧线的终点角度,而不是根据对 drawFrame 的调用次数计算弧线。

答案 1 :(得分:2)

要制作一个使用1分钟绘制圆圈的动画,不需要使用rAF,因为这只会产生额外负荷,即使我个人推荐rAF用于大多数情况。

在这种情况下,监视器同步不是那么重要setInterval(和setTimeout)可能是更好的选择。

以下是每分钟绘制一个圆圈的修改代码。它基于实际时间戳,因此时间非常准确。这里的间隔设置为120毫秒,但这应该与圆的圆周有关,因为这将决定在该时间范围内要绘制多少像素,因为重叠的像素赢了&#39 ;是可见的(这里忽略子像素)。随意根据需要调整时间。

<强> Modified fiddle here

现在设置如下(小提琴上不需要window.onload,所以我删除它,但当然如果你在最后一页的标题中加载脚本,你需要把它放回去)。 var名称可能更好,但我保留了一些原始名称:

start_angle = 1.5 * Math.PI, /// common offset (north)
end_angle   = 2 * Math.PI,   /// ends full circle in radians
increase_end_angle = 0,      /// current angle incl. offset
radius = 50,
startTime = (new Date()).getTime(),  /// get current timestamp
diff;                        /// used for timestamp diff

我们还在循环外部移动静态设置以节省一些CPU周期(实际设置笔划样式等。如果一直设置则会产生效果,因此这是更优化的)。没有必要使用save / restore,因为我们在其他地方需要的out循环期间不会更改很多变量:

context.lineWidth = 6;
context.lineCap = "round";

主要功能是根据实际时间重置圆圈:

setInterval(anim, 120); /// 120 for demo, use Ø and time to find optimal timeout

function anim() {

    /// calc difference between initial and current timestamp
    diff = (new Date()).getTime() - startTime;
    diff = diff / 60000; /// 60000ms = 60s, now we have [0, 1] fractions

    /// final angle
    increase_end_angle = start_angle + end_angle * diff;

    /// draw circle
    context.beginPath();
    context.arc(x, y, radius, start_angle, increase_end_angle);
    context.stroke();

    /// check diff fraction
    if (diff >= 1) { /// if diff >= 1 we have passed 1 minute
        /// update time and new radius
        startTime = (new Date()).getTime();
        radius += 10; /// add to current radius
    };
}

理想情况下你会清除每个绘制的当前圆圈,以保持抗锯齿像素看起来更平滑,因为重绘在顶部最终会因alpha通道而移除它。

当然这意味着你需要在增加半径时做一些额外的步骤,例如将当前内容绘制到后面的画布以保留已绘制的圆圈。

更新:您还可以制作画布&#34;高分辨率&#34;通过设置画布来减少arc方法的粗糙:

 canvas.width = wantedWidth * 2;
 canvas.height = wantedHeight * 2;

 canvas.style.width = wantedWidth + 'px'
 canvas.style.height = wantedHeight + 'px';

请记住相应地缩放所有坐标和尺寸(x2)。

<强> Updated fiddle running with high-resolution canvas

更新:解决其他问题:

  

1)如何将所有内容乘以2(即比例)会使一切变得更清晰。

&#34;高分辨率模式&#34;会发生什么?我们使用的画布大小是初始画布的两倍,但是通过应用额外的样式(CSS),我们将整个画布缩小到第一个尺寸。

然而,现在在同一区域中有两倍的像素,并且由于子像素化,我们可以利用它来获得更好的分辨率&#34;。但与此同时,我们还需要扩展所有内容,以便将其恢复到与使用双倍大小的画布之前相同的位置。

它就像400x400的形象。如果以400x400显示此值,则像素比率为1:1。如果您改为使用800x800的图像但是将其尺寸缩小到400x400,则图像中仍然会有800x800像素,但那些无法显示的图像(因为显示器无法显示半像素)会被内插到让它看起来有一个半像素。

对于形状,你可以获得更好的形状版本,因为它首先消除锯齿,然后用它周围的内插进行插值,使它看起来更平滑(正如你在演示中看到的那样)。

  

2)我知道setInterval(anim,120); //这部分......比较   这就是为什么圆圈在60秒内完成?而且我永远都是   使用setInterval时存在疑问。因为它确实提供了混蛋   过了一段时间。虽然不是这种情况。我想要我的动画   当使用RAF时总是发生停止。但是raf会很棒   为了优化。有一点混乱,但我想我会去   通过setInterval方式。

由于setIntervalsetTimeout无法同步到显示器的VBLANK,因此首先要采取行动。 VBLANK来自旧的CRT电视,当扫描内部显示器屏幕的光束时,开始一个新的帧。要正确同步,请同步到VBLANK间隙。这适用于与视频相关的所有设备。计算机。

然而,由于这需要定时器的浮点分辨率(在60Hz的情况下为16.7ms),这对于这些定时器是不可能的。在两者之间不时会出现一个舍入误差,导致在循环中跳过一个帧,从而产生混乱 - 导致混乱。来自rAF(requestAnimationFrame),它能够同步到显示器的刷新率,但不仅仅是因为这个原因。它更低效,更高效,也可以降低功耗。

如果您不需要每秒60次监视器同步,那么使用不准确的计时器是没有错的 - 就像在这种情况下您更依赖于整个分钟绘制的圆的半径。如果您使用了rAF,那么在此期间您可能会多次绘制相同的像素而无法看到任何变化(尽管子像素会启动但仍然允许对这些像素中的某些像素进行小的视觉变化)。所以这里的rAF并没有达到目的,因为你会做很多不必要的抽奖,这些抽奖不会对屏幕产生影响。

setInterval每个SE都不错,但对于动画来说,在需要不断更新的地方通常太不准确了。它的作用是创建一个事件并将其放入浏览器的事件队列中。如果可能,事件大约在超时时执行(队列包含许多种事件,例如重绘,函数调用等)。这里不够精确但准确,因为我们不依赖于它何时超时,而是使用更新弧时的实际时间。因此,为此目的,我们以非常小的增量绘制,这将掩盖微妙的不准确性。

这并不意味着其他因素可能会在更新中引发混乱,但这也适用于rAF。没有什么是完美的。

至于时间120毫秒 - 这只是我开始的一个数字。它是一个没有太多改变平滑度的数字。然而,这并不是完成圆圈的原因。

完成圆圈的是时间差"Now time" - "Start time"。在这种情况下,这将产生60,000毫秒的差异,因为我们每轮使用60秒。所以为了得到一个有用的数字,我们除以60000,所以得到的数字在0.0和1.0之间,我们可以直接乘以角度,当diff为1时得到100%的角度。

理想的计时器是使用相对于圆周长的时间。这将决定每个新像素之间存在多少时间,因此您可以再次将其除以10以考虑子像素。这将是最佳的,因为在更新的结束点不会重叠,但每次触发循环时都会绘制一个新的像素。

  

3)这个问题有点困难,我正在研究它   如果我无法做到这一点,我会接受你的建议。它的交易   与一些json,创建多个实例并停止动画   数据停止来的时候。明天我会试的,现在太累了。

为此我建议开一个新问题。似乎元答案超出了主要答案..: - o: - )