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.
Related
I have the most simple and straightforward animation with canvas:
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.canvas.width = 700;
ctx.canvas.height = 300;
var x = 0;
var update = function() {
x = x + 6;
}
var draw = function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(x, 10, 30, 30);
}
let lastRenderTime = 0
const frameRate = 60;
function main(currentTime) {
window.requestAnimationFrame(main)
const secondsSinceLastRender = (currentTime - lastRenderTime) / 1000
if (secondsSinceLastRender < 1 / frameRate) return
lastRenderTime = currentTime
update()
draw()
}
window.requestAnimationFrame(main)
<canvas id="canvas"></canvas>
It's just a rectangle moving from left to right.
However, even on my powerful PC, it's running inconsistently (you can see it's not smooth enough for 60 fps and also the speed is varying).
Is there something I'm doing wrong or is this just how canvas works?
Yes you are doing a few things wrong.
As a general rule, you should not increment the distance by a fixed amount, instead use a delta-time to determine by how much your object should have moved since the last frame.
This is because requestAnimationFrame(rAF) may not fire at regular intervals, for instance if the browser has a lot of things to do in parallel the next rAF loop may get delayed.
And anyway, you can't be sure at which rate rAF callbacks will fire; this will depend on the user's monitor's refresh-rate.
Here you are trying to set up a maximum frame rate of 60FPS, which I assume you thought would allow you to use a fixed increment value, since this code is supposed to control the frame-rate.
But this code would work only where the frame-rate is a multiple of the target FPS (e.g 120Hz, 240Hz). Every other frame rate will suffer from this code, and since as we said before the frame-rate should not be thought as being stable, even 120Hz and 240Hz monitors would suffer from it.
(note that on monitors where the refresh rate is lower than 60Hz, this code won't help them catch up on their delay either.)
Let's take a 75Hz monitor as an example (because it's actually quite common and because it makes for a good example), without anything interfering with the page and thus a "stable" frame-rate.
Every frame should have a duration of 1s/75 -> ~13.33333ms.
Before updating the object's position, your code checks if the duration of the frame is above 1s/60 -> ~16.66666ms.
On this 75Hz monitor every single frame will fail this condition, and thus the position will get updated only at the next frame:
1st frame
2nd frame
3rd frame
4th frame
clock time
13.33333ms
26.66666ms
39.99999ms
53.33332ms
last-paint
0ms
0ms
26.66666ms
26.66666ms
time-from-last-paint
13.33333ms
26.66666ms
13.33333ms
26.66666ms
status
discarded
painted
discarded
painted
x position
0px
6px
6px
12px
When on a 60Hz monitor with same stable conditions it would have been
1st frame
2nd frame
3rd frame
4th frame
clock time
16.66666ms
33.33333ms
49.99999ms
66.66666ms
last-paint
0ms
16.66666ms
33.33333ms
49.99999ms
time-from-last-paint
16.66666ms
16.66666ms
16.66666ms
16.66666ms
status
painted
painted
painted
painted
x position
6px
12px
18px
24px
So you can see how after 50ms, the 75Hz setup has its x value still at 6px when it should already be at 18px in optimal conditions, and how we end up painting only at 37.5FPS instead of the targeted 60FPS.
You may not be on 75Hz monitor, but on my macOS Firefox, which does calculate rAF's rate from the CPU instead of looking at the monitor's refresh-rate, I end up in a situation even worse, where frames takes about 16.65ms, meaning that to traverse the canvas it takes literally twice the time it would take without your frame-rate restriction.
In order to avoid that, use a delta-time to determine the position of your object. This way, no matter the delay between two frames, no matter the monitor's refresh-rate etc. your object will get rendered at the correct position and even if you drop a frame or two, your animation won't jump or get stuck.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.canvas.width = 700;
ctx.canvas.height = 300;
var x = 0;
const px_per_frame_at_60Hz = 6;
const px_per_second = (px_per_frame_at_60Hz * 60);
var update = function( elapsed_time ) {
const distance = elapsed_time * px_per_second;
x = (x + distance) % canvas.width;
}
var draw = function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(x, 10, 30, 30);
}
let lastRenderTime = 0
const frameRate = 60;
function main(currentTime) {
const secondsSinceLastRender = (currentTime - lastRenderTime) / 1000
update( secondsSinceLastRender );
draw();
lastRenderTime = currentTime;
// better keep it at the end in case something throws in this callback,
// we don't want it to throw every painting frames indefinitely
window.requestAnimationFrame(main)
}
window.requestAnimationFrame(main)
<canvas id="canvas"></canvas>
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've done this sort of programming before but It was a long while back, and despite trying for a while now, I am unable to get this working. I've tried loads of other similar codes that I've found on the internet but they don't work exactly the way I want it to! I basically want a 155x55 canvas, with a 50x50 image moving across it, simple! Despite how simple it sounds... I'm struggling... I've tried adapting my previous code but that was for bouncing balls and it was a long time ago. I'll appreciate any help. Thanks!
var myCanvas = document.getElementById("myCanvas");
var ctx = myCanvas.getContext("2d");
var speed = 1;
a = new Image();
a.src = "http://www.animated-gifs.eu/category_cartoons/avatars-100x100-cartoons-spongebob/0038.gif";
function frameRate(fps) {
timer = window.setInterval( updateCanvas, 1000/fps );
}
function updateCanvas() {
ctx.fillRect(0,0, myCanvas.width, myCanvas.height);
draw();
}
function draw() {
/* Add code here to randomly add or subtract small values
* from x and y, and then draw a circle centered on (x,y).
*/
var x = 0 + speed;
var y = 20;
if (x > 150) {
x == 1;
}
ctx.beginPath();
ctx.drawImage(a,x,y,100,100);
}
/* Begin animation */
frameRate(25);
Fiddle Link:
https://jsfiddle.net/th6fcdr1/
The problem you have is that your variable x and y are always reset to 0 and 20. Your speed is 1 so your x is always 1.
Since you never update the x position and always reset it to 0. What you could do is to increase the variable speed by 1 at the end of the frame.
speed += 1
At first, you'll have:
x = 0 + 1
then
x = 0 + 2
... and so on.
Then you'll have to check for speed being above 150 and reset speed to 1.
Then I suggest renaming speed by posX which is more accurate. Also, instead of using setInterval you should be using requestAnimationFrame(). And instead of incrementing the posX by 1, you should be incrementing the posX by speed * elapsedTime to get a fluent move and stable speed move which doesn't depend on the framerate.
In the end, you'd have this:
posX += speed * elapsedTime
var x = posX
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/
I wrote a quick program to bounce a ball on the screen. Everything works, but the image is prone to flickering and is not smooth.
I suspect that the image flickers because the velocity is significant at the bottom of the screen.
I was wondering if anybody had any ideas on how to interpolate the motion of the ball to reduce the flicker.
Called to update the position
this.step = function()
{
thegame.myface.y = thegame.myface.y + thegame.myface.vSpeed;
if (thegame.myface.y > thegame.height)
{
thegame.myface.vSpeed = -thegame.myface.vSpeed;
}
else
{
thegame.myface.vSpeed = thegame.myface.vSpeed + 1;
}
}
},
Called to redraw
draw: function()
{
//clears the canvas
thegame.ctx.clearRect(0,0,thegame.width,thegame.height);
//draw the objects
thegame.ctx.drawImage(thegame.imgFace,this.x-this.width/2,this.y-this.height/2);
return;
},
Call framework in index.html
<script type="text/javascript">
thegame.init(450,450);
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
(function()
{
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x)
{
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelRequestAnimationFrame = window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
{
var f = function(callback, element)
{
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16-(currTime-lastTime));
var id = window.setTimeout(function()
{
callback(currTime+timeToCall);
}, timeToCall);
lastTime = currTime+timeToCall;
return id;
};
window.requestAnimationFrame = f;
}
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id)
{
clearTimeout(id);
};
}());
(function gameloop()
{
thegame.update();
requestAnimationFrame(gameloop);
thegame.draw();
})();
</script>
edit definition for thegame
init: function(width, height)
{
var canvas = $("<canvas width='"+width+"' height='"+height+"'></canvas>");
canvas.appendTo("body");
thegame.ctx = canvas.get(0).getContext("2d");
thegame.width = width;
thegame.height = height;
thegame.imgFace = new Image();
thegame.imgFace.src = "face.png";
thegame.myface = new thegame.makeFace(width/2,height/2);
},
It's about visual perception. First find out at what rate the game loop is called by the browser via requestanimationframe. If it's Chrome its task manager will help. If not, calc the rate by yourself with timestamps. It should be at least 60 times per second. If the browser is running at this rate and the movement is still not smooth, the speed is simply too high for that rate.
However, you have options to trick the perception of motion. One is to make the image smaller (simple), another is motion blur (complicated). To do the latter, you basically run the game hidden at double speed and draw two blended frames onto the visible canvas. Or at same speed and a bit simpler, keep track of the last two images and draw both with 50% alpha at the canvas. If you want more background info follow the discussion why latest hobbit movie was filmed at 48 instead of the usual 24 frames per second.
If it appears the image is horizontally chopped/cut in half then the browser is not properly synchronized to the monitor. In this case make sure the vertical sync (vsync) isn't overridden somewhere by the system or display options.
How large is your canvas? Not sure if it will solve your flicker issue, but an additional optimization you can try is to modify your clearRect() call to only clear the region which is dirty, before each update.
Ex: thegame.ctx.clearRect(this.x-this.width/2, this.y-this.height/2, thegame.imgFace.width, thegame.imgFace.height);