How can I increase the speed of my canvas animation and still get a smoothly moving object?
In my example a boy is moving from the top to the bottom of the canvas. The movement seems to jolt, the bigger the y-step is. How can I avoid the jolts and still have a fast movement to the bottom?
Fiddle
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
(function animloop() {
requestAnimFrame(animloop);
redraw();
})();
function redraw() {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.beginPath();
ctx.rect(20, y, 90, 90);
ctx.fillStyle = "red";
ctx.fill();
y += 5;
}
You can increase the speed your canvas animation and still get a smoothly moving object by reducing the distance travelled per frame and increasing the frame rate.
RequestAnimationFrame is a performant way of animating at a reasonable framerate given your hardware but it fixes the framerate.
You can have more control over the framerate using setInterval or setTimeout instead, which accepts a second integer argument that determines milliseconds till next callback (i.e. framerate).
setInterval(function () {
redraw();
}, 2);
http://jsfiddle.net/uq14c6bu/2/
Related
I've been looking up on this for hours and hours but I couldn't figure out how to fit it to my needs.
I'm trying to make it so that an animation is played at a specific X and Y location, after being triggered. (For example, an explosion animation plays after a tank is destroyed)
I have a function like this:
var explosion = new Image();
explosion.src = "https://i.imgur.com/gWjqlKe.png";
function animate(img, x, y, width, height, steps){
steps = Math.floor(steps);
context.clearRect(x, y, width, height);
context.drawImage(img, width * step, 0, width, height);
step += .3;
requestAnimationFrame(animate(img, x, y, width, height, steps));
}
And then when I needed to call it, I did:
animate(explosions, tank.x, tank.y, 100, 100, 10);
But nothing happens when I destroy a tank.
I based my code off of this tutorial: https://www.youtube.com/watch?v=yna816VY8rg
Any help would be appreciated! Thanks so much!
EDIT:
I'm trying out setInterval now, but it still doesn't work...
var explosion = new Image();
explosion.src = "https://i.imgur.com/gWjqlKe.png";
// Animation Functions
function animate(img, x, y, width, height, endframe){
var frame = 0;
frame = setInterval(animation(frame, img, x, y, width, height), 1000);
if (frame >= endframe){
clearInterval(frame)
}
}
function animation(frame, img, x, y, width, height){
ctx = gameArea.context;
ctx.drawImage(img, x * frame, y, width, height);
frame ++;
return frame;
EDIT 2:
I realized from my Reddit that I made a mistake in the requestAnimationFrame and setInterval, so I edited again but it still does not work:
function animate(img, x, y, width, height, endframe){
var frame = 0;
frame = setInterval(function() {
animation(frame, img, x, y, width, height);
}, 1000);
if (frame >= endframe){
clearInterval(frame)
}
}
function animation(frame, img, x, y, width, height){
ctx = gameArea.context;
ctx.drawImage(img, x * frame, y, width, height);
frame ++;
return frame;
}
function animate(img, x, y, width, height, steps){
steps = Math.floor(steps);
context.clearRect(x, y, width, height);
context.drawImage(img, width * step, 0, width, height);
step += .3;
requestAnimationFrame(animate(img, x, y, width, height, steps));
}
One main loop to rule them all.
Using event timers to start animation loops via requestAnimationFrame will cause you all sort of problems. requestAnimationFrame will fire for the next frame in the same order as the last frame. If your timeout gets in during the frame before any other frames it will be the first function to draw to the canvas, then the other frames will draw over the top.
As nnnnnnn pointed out in the comment use only one game loop and save yourself a lot of unneeded complexity in getting the order of renders correct.
requestAnimationFrame time argument
requestAnimationFrame provides a timer in as the first argument. This timer is the current frame time, not the actual time. This lets you compute times from the V sync (display refresh)
Example of timing and animation
To animate some FX for a set time record the start time, set a duration and animate to the time given in the frames argument.
The example shows a simple explode timer used in a main loop. You begin the timer when needed supplying the current frame time. While it is active you give it the current time, It updates its own relative time, and when done the active flag is set to false. You render your animation via its current time which will be from 0 to length
This is only one animation, you would use a stack of such time objects, adding to the stack for each timed animation.
const explode = {
start : null,
length : 1000,
current : 0,
active : false,
begin(time,length = this.length){
this.start = time;
this.length = length;
this.active = true;
this.current = 0;
},
getCurrent(time){
this.current = time - this.start;
this.active = this.current <= this.length;
return this.current;
}
}
var tankDead = false;
function mainLoop(time){ // time is supplied by requestAnimationFrame
if(tankDead ) {
tankDead = false;
explode.begin(time,2000); // two seconds
}
if(explode.active){
explode.getCurrent(time);
if(explode.active){
// explode.current is the time
// draw the animation at time explode.current
} else {
// explosion is complete
}
}
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
Timing animations via a tick.
Though I prefer the fixed frame rate method and just count down a tick timer
var tankDead = false;
var explodeTick = 0;
function mainLoop(time){ // time is supplied by requestAnimationFrame
if(tankDead ) {
tankDead = false;
explodeTick = 60 * 2; // two seconds
}
if(explodeTick > 0){
explodeTick -= 1;
// draw the animation at time explosion
}
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
Much simpler to manage, and nobody will notice if you drop a frame or two and it takes a fraction of a second longer.
I am experimenting with drawing using javascript and the canvas element..my goal now is to draw a circle and gradually increase the opacity; I have this code:
http://codepen.io/anon/pen/zrVvOQ
Which seems to work, but the circle has rough edges; I found I need to clear the canvas each time the frame is redrawn, but the attempts I have made have not quite worked...any suggestions on how to?
window.onload = function draw(){
var frame1 = document.getElementById('frame1');
if (frame1.getContext){
var ctx = frame1.getContext('2d');
var centerX = frame1.width / 2;
var centerY = frame1.height / 2;
var radius = 50;
var alpha = 1.0;
/*call function over and over */
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
var rendergreen = function()
{
var opacityValue = 0;
opacityValue += 0.03;
ctx.fillStyle = 'rgba(68,107,62, ' + opacityValue + ')';
animate();
ctx.arc(50, centerY, radius, 0, 2 * Math.PI, false);
ctx.clip();
ctx.fill();
ctx.closePath;
function animate() {
if (opacityValue < 1) {
opacityValue += 0.3;
} else {
opacityValue = 1;
}
}
requestAnimationFrame(rendergreen);
}
rendergreen();
}
}
You say the circle has "rough edges". That's pixellation and is inherent in using canvas to draw, which is a bit-mapped style of graphics. That means that you essentially can't get higher resolution than a single pixel. Contrast that with svg which is vector-based. An svg image can be magnified a thousand times and still have a smooth edge. I've shown an svg circle next to the canvas circle so that you can see the difference. It becomes much more apparent if you zoom in with your browser. There are pro's and con's to using canvas vs svg, too much to go into here, but it's worth looking into if you're really concerned.
In terms of changing the opacity of the circle, you've got several problems with your approach. With the way you've written it, you're actually not changing the opacity. Instead, you're drawing the same very transparent circle many times over top of each other so that by the end it looks opaque, giving the impression that you are gradually increasing the transparency of a single circle. Notice that you're setting your opacity to zero in each drawing iteration, then incrementing it to 0.05 (note that there are differences in the code in your question versus in the codepen that you linked to...I'm referring to the codepen version), then drawing it (so it will always be drawn at opacity 0.05), then further changing the value of the variable opacityValue which is never used in the drawing. The example below shows a relatively simple example of what I think you were trying to achieve. Note that I've deliberately made the 'clearRect' too small so that you can see how not clearing the canvas each time allows semi-transparent drawings to "pile up". This also allows you to see that the blockiness gets worse if you overlay many semi-transparent images. e.g. Compare the left and right sides of the canvas circle. The part of the circle that is cleared every time ends up looking smoother because of anti-aliasing, but the overlaid images have the smoothing effects of anti-aliasing effectively destroyed.
window.onload = function draw() {
var frame1 = document.getElementById('frame1');
if (frame1.getContext) {
var ctx = frame1.getContext('2d');
var opacityValue = 0;
var render = function() {
ctx.clearRect(0, 0, 80, 80); // deliberately set too small
ctx.beginPath();
opacityValue += 0.01;
ctx.fillStyle = 'rgba(68,107,62, ' + opacityValue + ')';
ctx.arc(60, 60, 50, 0, 2 * Math.PI, false);
ctx.fill();
ctx.closePath;
requestAnimationFrame(render);
}
render();
}
}
<canvas id="frame1" width="120" height="120"></canvas>
<svg width="120" height="120">
<circle cx="60" cy="60" r="50" fill="#446B3E"></circle>
</svg>
I am drawing an arc which increases gradually and turns in to a circle.On completion of animation(arc turning in to a circle) i want to draw another circle with increased radius with the previous circle persisting and the second animation continuing.
Arc to circle fiddle
After the circle is drawn,it gets washed out which is something that I dont want and continue the second animation.
Some unnecessary animation appears after the completion.
What should I do?
MyCode:
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
draw(radius+10);//draw from same origin and increasd radius
}
},66);
window.onload=draw(30);
UPDATE:when should i clear the interval to save some cpu cycles and why does the animation slows down on third circle ??
First of all, about the flicker: you are using setInterval and not clearing it for the next draw(). So there’s that.
But I’d use a completely different approach; just check the time elapsed since the start, and draw an appropriate number of circles using a loop.
var start = new Date().getTime();
var timePerCircle = 2;
var x = 190, y = 140;
function draw() {
requestAnimationFrame(draw);
g.clearRect(0, 0, canvas.width, canvas.height);
var t = (new Date().getTime() - start) / 1000;
var circles = t / timePerCircle;
var r = 30;
do {
g.beginPath();
g.arc(x, y, r, 0, Math.PI * 2 * Math.min(circles, 1));
g.stroke();
r += 10;
circles--;
} while(circles > 0);
}
draw();
This snippet from your code has some flaw.
if(dynamic_end_angle>3.5*Math.PI){ //condition for if circle completion
draw(radius+10);//draw from same origin and increased radius
}
The recursive call to draw() will continue to run after the first circle was drawn completely. This is why the performance will be slow down immediately. You need to somehow block it.
I did a simple fix, you can polish it if you like. FIDDLE DEMO
My fix is to remove context.clearRect(0, 0, 500, 400); and change the new circle drawing logic to:
if (dynamic_end_angle > 3.5 * Math.PI) { //condition for if circle completion
increase_end_angle = 0; // this will prevent the draw() from triggering multiple times.
draw(radius + 10); //draw from same origin.
}
In this stackoverflow thread, it mentions how to make it more smooth. You'd better use some drawing framework since the optimization needs a lot of work.
When should I clear the interval to save some cpu cycles?
Better yet not use an interval at all for a couple of reasons:
Intervals are unable to sync to monitor's VBLANK gap so you will get jerks from time to time.
If you use setInterval you risk stacking calls (not high risk in this case though).
A much better approach is as you probably already know to use requestAnimationFrame. It's less CPU hungry, is able to sync to monitor and uses less resources in general even less if current tab/window is not active.
Why does the animation slows down on third circle ??
Your drawing calls are accumulating which slows everything down (setInterval is not cleared).
Here is a different approach to this. It's a simplified way and uses differential painting.
ONLINE DEMO
The main draw function here takes two arguments, circle index and current angle for that circle. The circles radius are stored in an array:
...,
sa = 0, // start angle
ea = 359, // end angle
angle = sa, // current angle
oldAngle = sa, // old angle
steps = 2, // number of degrees per step
current = 0, // current circle index
circles = [70, 80, 90], // the circle radius
numOfCircles = circles.length, ...
The function stores the old angle and only draws a new segment between old angle and new angle with 0.5 added to compensate for glitches due to anti-alias, rounding errors etc.
function drawCircle(circle, angle) {
angle *= deg2rad; // here: convert to radians
/// draw arc from old angle to new angle
ctx.beginPath();
ctx.arc(0, 0, circles[circle], oldAngle, angle + 0.5);
ctx.stroke();
/// store angle as old angle for next round
oldAngle = angle;
}
The loop increases the angle, if above or equal to end angle it will reset the angle and increase the current circle counter. When current counter has reach last circle the loop ends:
function loop() {
angle += steps;
/// check angle and reset, move to next circle
if (angle >= ea - steps) {
current++;
angle = sa;
oldAngle = angle;
}
drawCircle(current, angle);
if (current < numOfCircles)
requestAnimationFrame(loop);
}
I use this on a lower canvas:
var requestAnimFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000/60);
};
I am using this for a canvas layer that is above the lower one:
function DrawSpawnAnimation() {
anim();
}
function anim() {
ctxAnimation.drawImage(spriteSheet, ExplodeFrame * 100, 2740,100,100,explodeX,explodeY,100,100);
if (ExplodeFrame < 5) {
ExplodeFrame++;
setTimeout(anim, 500);
}
//alert("Show current frame of animation"); - this shows the animation works, it just does
// not show it one frame per half second.
}
My problem is the animation just flickers in a millisecond on screen. Is the lower canvas refresh messing it up?
Here's how to use 1 RAF ticker to animate 2 canvases.
The RAF ticker will fire 60 times per second.
The top canvas will animate 30x per second (Every 2nd frame when TopFrameCount drops to 0).
The bottom canvas will animate 2x per second (Every 30th frame when BottomFrameCount drops to 0)
var RAFfps = 60;
var TopFPS=30;
var BottomFPS=2;
var TopFrameCount=(RAFfps/TopFPS)-1;
var BottomFrameCount=(RAFfps/BottomFPS)-1;
var counter2fps=0;
function draw() {
setTimeout(function() {
requestAnimFrame(draw);
// if the top canvas counter has expired,
// animate the top canvas
if(--TopFrameCount<=0){
// put top canvas drawing stuff here
// reset the top counter
TopFrameCount==(RAFfps/TopFPS)-1;
}
// if the bottom canvas counter has expired,
// animate the bottom canvas
if(--BottomFrameCount<=0){
// put bottom canvas drawing stuff here
// reset the top counter
BottomFrameCount=(RAFfps/BottomFPS)-1;
}
}, 1000 / fps);
}
It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
I have created a simple canvas animation and I heard that it is better to use 'request animation frame' than 'setinterval', but I'm not sure how to do it?
This is how it looks like at the moment:
http://jsfiddle.net/rC7zJ/
var width = 48;
var height = 87;
var speed = 100; //ms
var frames = 1; // Total frames - 1, as frame start from 0 not
var currentFrame = 0;
canvas = document.getElementById("CanvasAnimate");
ctx = canvas.getContext("2d");
imageTadPole = new Image()
imageTadPole.src = 'https://dl.dropbox.com/u/19418366/tadpole.png';
function draw(){
ctx.clearRect(0, 0, width, height);
ctx.drawImage(imageTadPole, width * currentFrame, 0, width, height, 0, 0, width, height);
if (currentFrame == frames) {
currentFrame = 0;
} else {
currentFrame++;
}
}
setInterval(draw, speed);
Any help would be appreciated!
Always start with Paul Irish’s great cross-browser shim for requestAnimationFrame
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
Then just code an animation function that:
Updates the variables that drive your animation( location, velocity, color changes, etc)
Draws the current frame using canvas’ context
Keeps the animation alive for the next frame by re-calling requestAnimFrame
Here’s example skeleton code:
function animate() {
// update animation variables
X+=20;
Velocity +=10;
// clear the canvas and draw the current frame
// based on the variables you just updated
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.rect(x,y,75,50);
// request new frame to keep the animation going
requestAnimFrame( animate );
}
Here is how to throttle down your animation (like FPS--but not):
// set a countdown "throttle" that will slow animate() down by 4X
var notEveryTime=3;
function animate() {
// if notEveryTime hasn't counted down to 0
// then just don't animate yet
if(--notEveryTime>0){ return; };
// update animation variables
X+=20;
Velocity +=10;
// clear the canvas and draw the current frame
// based on the variables you just updated
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.rect(x,y,75,50);
// request new frame to keep the animation going
requestAnimFrame( animate );
// reset notEveryTime
notEveryTime=3;
}
And do read Anreas' good suggestion of: http://paulirish.com/2011/requestanimationframe-for-smart-animating/
What marke said, but if you want to control the FPS, wrap your requestAnimationFrame call in a setTimeOur function, like so:
var fps = 15;
function draw() {
setTimeout(function() {
requestAnimationFrame(draw);
// Drawing code goes here
}, 1000 / fps);
}
It is a good idea to use requestAnimationFrame, even if you're fixing your frame rate, as it
will provide CPU throttling when the page is inactive a
gives you the behind-the-scene optimisations requestAnimationFrame provides.
A good page on Request Animation Frame can be found here.