FIDDLE

What I am doing:I am drawing a cirlce from an arc using set interval.After a circle is drawn,i am drawing another circle with slightly increased radius as seen in fiddle.

What I want to do:I want to achieve same functionality,but a circle should be completed at 1min(i.e 60 sec to draw a circle.) using requestAnimationFrame and avoiding setInterval.

I know what RAF is,but unable to implement it

60 sec...RAF...Circle.?? My code:

    //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);

Updated questions for Ken only:

1)how does multiplying everything by 2(i.e scale) makes everything sharper.

2)i dint understood the setInterval(anim, 120);//this part..ratio..is this why circle gets completed at 60 seconds? and again i am always doubtful when using setInterval.Reason being it does provides jerk after some time.Not in this case though.I dint wanted my animation to stop which always happened when using RAF.But again raf would be great for optimization.So a little confusion was there,but i guess i will go by the setInterval way.

3)this question is a little difficult and i am working on it for now.In case I am not able to do it,i would take your advice.It deals with some json,creating multiple instances and stopping the animation when data stops coming.I will try that tomorrow,too tiredd now.

Thanks for the answer ken!!!exactly what i wanted.

有帮助吗?

解决方案

To produce an animation that will draw a circle using 1 minute there is no need to use rAF as this just generate extra load even though I personally recommend rAF for most cases.

In cases like this however where monitor sync is not that critical setInterval (and setTimeout) are probably better choices in regard to load.

Here is modified code that draws a circle per minute. It is based on actual time stamp so the timing is pretty accurate. The interval here is set to 120 ms but this should really be related to the circle's circumference as this would determine how many pixels are to be draw within that time frame as overlapping pixels won't be that visible (disregarding sub-pixeling here). Feel free to adjust the time out as needed.

Modified fiddle here

The setup is now as follows (window.onload is not needed on fiddle so I removed it, but of course you need to put it back if you load the scripts in header in your final page). The var names could be better but I kept somewhat the original names:

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

We also move static settings outside the loop to save some CPU cycles (actually setting stroke style etc. do have an effect if set all the time so this is more optimal). There is neither any need to use save / restore as we are not changing a lot of variables during out loop that is needed elsewhere:

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

The main function is resetting the circles based on actual time:

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
    };
}

Ideally you would clear the current circle for each draw to keep the ant-aliasing pixels to get a smoother look as redrawing on top will eventually remove this due to the alpha channel.

Of course that would mean you needed to do some extra steps when radius is increased such as drawing the current content to a canvas in the back to keep the already drawn circles.

Update: You can also make the canvas "high-resolution" to reduce the crud-ness of the arc method by setting the canvas:

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

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

Just remember to scale all coordinates and sizes accordingly (x2).

Updated fiddle running with high-resolution canvas

Update: to address the additional questions:

1)how does multiplying everything by 2(i.e scale) makes everything sharper.

What happens in the "high-resolution mode" is that we are using a canvas twice the size of the initial one, but by applying the extra styling (CSS) we scale the whole canvas back down to the first size.

However, now there are twice as many pixels in the same area and due to sub-pixeling we can utilize this to gain better "resolution". But at the same time as there are twice as many we also need to scale everything to get it back in the same position as before we used a double sized canvas.

It's like an image of lets say 400x400. If you show this at 400x400 then the pixel ratio is 1:1. If you instead used an image of 800x800 but forced its size down to 400x400 there would still be 800x800 pixels in the image but those that can't be shown (as a monitor can't show half pixels) are interpolated to make it appear there is a half pixel there.

For shapes however you can achieve a better detailed version of the shape as it is first anti-aliased then interpolated with what surrounds it making it look smoother (as you can see in the demo).

2)i dint understood the setInterval(anim, 120);//this part..ratio..is this why circle gets completed at 60 seconds? and again i am always doubtful when using setInterval.Reason being it does provides jerk after some time.Not in this case though.I dint wanted my animation to stop which always happened when using RAF.But again raf would be great for optimization.So a little confusion was there,but i guess i will go by the setInterval way.

The jerks to take that first is due to setInterval and setTimeout are not capable of syncing to the monitor's VBLANK. VBLANK comes from the old CRT TVs when the beam that scanned the monitor screen on the inside, started a new frame. To sync properly you sync to the VBLANK gap. This goes for all equipment related to video incl. computers.

However as this would require a float resolution of the timer (16.7ms in case of 60Hz) this is not possible with these timers. In between for time to time you will get a rounding error sort of causing a frame to be skipped in the loop so-to-speeak - resulting in jerks. In comes rAF (requestAnimationFrame) which is capable of syncing to the monitor's refresh rate, but not only for this reason. It is more low-level and efficient and can also reduce power consumption as a result.

There is nothing wrong in using inaccurate timers if you don't need monitor sync (up to) 60 times per seconds - as in this case where you are more dependent on the radius of the circle drawn over a complete minute. If you used rAF you would probably draw over the same pixel many times during that time not able to see any change (though sub-pixeling would kick in and still allow a small visual change for some of those pixels). So here rAF doesn't serve a purpose as you would do many unnecessary draws that would not make a difference on the screen.

setInterval is not bad per-SE but for animation it is usually too inaccurate where you need constant update. What it does is to create an event and put it in the browser's event queue. When possible the event is executed at about the time it timed out for (the queue contains many sorts of events such as repaint, function callings etc.). Not super accurate but accurate enough here as we don't depend on when it times out but using the actual time when we update the arc. So for this purpose this works as we draw in very small increments that will mask the subtle inaccuracies.

That doesn't mean other factors may trigger jerks in the update but that goes for rAF as well. Nothing is perfect.

As to the time 120ms - this is just a number I started with. It's a number that didn't change the smoothness too much. It's not what completes the circle however.

What completes the circle is the difference in time "Now time" - "Start time". This would give a difference of 60,000 milliseconds in this case as we use 60 seconds per round. So to get a useful number we divide on 60000 so the number we get is between 0.0 and 1.0 which we can multiply directly with the angle to get 100% angle when diff is at 1.

The ideal timer is to use time in relation to the circumference of the circle. This would dictates how much time there would be between each new pixel so you could for example divide this on 10 again to consider sub-pixeling. This will be optimal as there won't be overlaps at the end point for the update but each time the loop is triggered a new pixel will be drawn.

3)this question is a little difficult and i am working on it for now.In case I am not able to do it,i would take your advice.It deals with some json,creating multiple instances and stopping the animation when data stops coming.I will try that tomorrow,too tiredd now.

For this I would suggest opening a new question. And it seems as the meta-answer out-grew the main answer.. :-o :-)

其他提示

While setInterval calls a function repeatedly, requestAnimationFrame schedules it to be called just once. If you want your function to be called repeatedly, you can call requestAnimationFrame again at the end of your function.

Below is an example based on your code. Note how requestAnimationFrame is called twice:

  1. At the very bottom, this calls drawFrame for the first time.
  2. At the end of the drawFrame function. After drawFrame completes its business, it invokes requestAnimationFrame to call itself again upon the next animation frame.

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);

Note that requestAnimationFrame does not guarantee your function to be called precisely 60 times per second. From the MDN documentation (emphasis mine):

You should call this method whenever you're ready to update your animation onscreen. This will request that your animation function be called before the browser performs the next repaint. That repaint may occur up to 60 times per second for foreground tabs (the exact rate is up to the browser to decide), but may be reduced to a lower rate in background tabs.

If you need to complete the arc within exactly one second, make sure to calculate the end angle of the arc based on the passing time, instead of on the number of calls to drawFrame.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top