The difference between interval animation and requestAnimationFrame - javascript

What is the difference between those two? requestAnimationFrame should be "intervaled" or invoked cca every 60milisecond on 60fps (depending on screen) and with setInterval you can set delay interval of invokation of function.
Yet i have made simple drawning animation with both , interval and requestAnimationFrame and it seems that interval function is smoother and work properly, while requestAnimation function broke browser (tested on mozilla/chrome and on 2 Pcs).
interval function:
function animate(x) {
var start = new Date();
var id = setInterval(function () {
var timepassed = new Date() - start;
var progress = timepassed / x.duration;
if (progress > 1) {
progress = 1;
}
var delta = x.delta(progress);
x.step(delta);
if (progress == 1) {
clearInterval(id);
}
}, x.delay);
}
requestAnimationFrame function:
function animate(x) {
var start = new Date();
var id = function () {
var timepassed = new Date() - start;
var progress = timepassed / x.duration;
if (progress > 1) {
progress = 1;
}
var delta = x.delta(progress);
x.step(delta);
requestAnimationFrame(id);
if (progress < 1) {
requestAnimationFrame(id);
}
}
requestAnimationFrame(id)
}
function to do drawning
function move(delta) {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
animate({
delay:10,
duration: 4000,
delta: delta,
step: function (delta) {
ctx.clearRect(0, 0, c.width, c.height);
ctx.beginPath();
ctx.strokeStyle="red";
ctx.lineWidth = 15;
ctx.arc(150, 150, 70, 0*Math.PI, delta*(2 * Math.PI));
ctx.font="40px Georgia";
ctx.fillText((delta*100).toFixed(0)+"%",95,150);
ctx.stroke(); ;
}
});
}
move(function(p){return p});
demo for interval : http://jsfiddle.net/Trolstover/5tmu4j6z/
demo for requestAnimationFrame : http://jsfiddle.net/Trolstover/5tmu4j6z/1

just comment out
//requestAnimationFrame(id);
var timepassed = new Date() - start
see this: http://jsfiddle.net/5tmu4j6z/2/

After you call animate setInterval will run starting at the first interval, while requestAnimation will run before the next repaint of your canvas. The reason requestAnimation runs again is because it's recursive.

Related

Javascript canvas "game". Hero mooves only one time

Im trying to make simple game in canvas. I made animation for hero using setTimeout() function. I check pressed keys with function moove(e):
Everything works pretty fine when i press leftarrow or rightarrow for the first time, but then hero doesnt moove. Any recomendations to the code is appreciated.
var cns = document.getElementById("can");
cns.height = 600;
cns.width = 300;
var ctx = cns.getContext("2d");
var hero = new Image();
hero.src = "images/hero.png";
hero.onload = function() {
ctx.drawImage(hero, 120, 570);
hero.xx = 120;
hero.yy = 570;
};
var intervalL, intervalR, intervalLL, intervalRR;
var keys = [];
function moove(e) {
keys[e.keyCode] = (e.type == "keydown");
if (keys[37]) {
clearTimeout(intervalR);
clearTimeout(intervalRR);
goLeft(hero);
} else {
clearTimeout(intervalL);
clearTimeout(intervalLL);
}
if (keys[39]) {
clearTimeout(intervalL);
clearTimeout(intervalLL);
goRight(hero);
} else {
clearTimeout(intervalR);
clearTimeout(intervalRR);
}
}
function goLeft(img) {
var x = img.xx,
y = img.yy;
function f() {
ctx.clearRect(img.xx, img.yy, img.width, img.height);
ctx.drawImage(img, x, y);
img.xx = x;
img.yy = y;
x -= 1.2;
if (x < -35) {
x = cns.width;
}
}
if (!intervalL) {
intervalL = setTimeout(function run() {
f();
intervalLL = setTimeout(run, 5);
}, 5);
}
}
Function goRight is similiar to goLeft.
Function moove is called in tag body onkeydown='moove(event)' onkeyup='moove(event)'.
You can check the project here: https://github.com/Fabulotus/Fabu/tree/master/Canvas%20game%20-%20dodge%20and%20jump
The reason it doesn't work the first time is because the first time through you are setting the position to its previous position (x = image.xx) then updating x after you draw. You should update the x value x -= 1.2 before calling drawImage
Here is a "working" version of your code:
var cns = document.getElementById("can");
cns.height = 170;
cns.width = 600;
var ctx = cns.getContext("2d");
var hero = new Image();
hero.src = "http://swagger-net-test.azurewebsites.net/api/Image";
hero.onload = function() {
ctx.drawImage(hero, cns.width-10, cns.height/2);
hero.xx = cns.width-10;
hero.yy = cns.height/2;
};
var intervalL, intervalR, intervalLL, intervalRR;
var keys = [];
function goLeft(img) {
function f() {
ctx.beginPath()
ctx.clearRect(0, 0, cns.width, cns.height);
ctx.drawImage(img, img.xx, img.yy);
img.xx--;
if (img.xx < -img.width) {
img.xx = cns.width;
}
}
if (!intervalL) {
intervalL = setTimeout(function run() {
f();
intervalLL = setTimeout(run, 5);
}, 5);
}
}
goLeft(hero)
<canvas id="can">
As you can see the function goLeft has been significantly simplified.
One recommendation: avoid the many setTimeout and clearTimeout instead use one setInterval to call a draw function that takes care of drawing everything on your game, all the other function should just update the position of your gameObjects.

When my stopwatch updates with setInterval, it blinks, how to make it smooth?

Hi i made a stopwatch in javascript and every time it updates, it deletes, and then after a second appears again and deletes. So every second it appears and disappears, making it blink. How can i make it appear until the next second updates, making a smooth transition.
Here is my code:
function GameTimer() {
var gameTimeMinutes = 0;
var gameTimeSeconds = 0;
var gameTime = "";
this.addTime = function() {
gameTimeSeconds += 1;
if (gameTimeSeconds < 10) {
gameTime = gameTimeMinutes + " : 0" + gameTimeSeconds;
} else {
gameTime = gameTimeMinutes + " : " + gameTimeSeconds;
}
if (gameTimeSeconds == 60) {
gameTimeSeconds = 0;
gameTimeMinutes++;
}
};
this.draw = function() {
graph.clearRect(0, 0, canvas.width, canvas.height);
var fontSize = 25;
graph.lineWidth = playerConfig.textBorderSize;
graph.fillStyle = playerConfig.textColor;
graph.strokeStyle = playerConfig.textBorder;
graph.miterLimit = 1;
graph.lineJoin = 'round';
graph.textAlign = 'right';
graph.textBaseline = 'middle';
graph.font = 'bold ' + fontSize + 'px sans-serif';
graph.strokeText(gameTime, 100, 30);
graph.fillText(gameTime, 100, 30);
};
this.update = function() {
this.addTime();
this.draw();
}.bind(this);
this.update();
}
var playerConfig = {textBorderSize: "1px", textColor: "black", textBorder: "solid"};
var canvas = document.getElementById("timer");
var graph = canvas.getContext("2d");
var gameTimer = new GameTimer();
setInterval(gameTimer.update, 1000);
<canvas id="timer" width="200" height="100"></canvas>
So just to clarify, i tried changing the interval time to ten seconds, and every ten seconds it appears and disappears, meaning that it is gone for the next ten seconds until it appears and disappears again. It doesn't stay there until the next update.
Thanks!
I believe that the flashing could be a result of setInterval not synchronizing with the screen's refresh rate. If so, requestAnimationFrame may be able to solve this.
This does use unnecessary system resources, but may solve your problem regardless.
Try this:
this.timeUntilUpdate = Date.now();
this.update = function(){
if(Date.now() - this.timeUntilUpdate > 1000){
this.addTime();
this.draw();
}
requestAnimationFrame(this.update);
}
And then at the end replace setInterval(gameTimer.update, 1000); with requestAnimationFrame(GameTimer.update) or possibly just GameTimer.update();

Javascript setInterval randomly stops

I'm using setInterval to call a function that animates a fractal on a HTML5 canvas. There is also a slider to allow the user to change the quality of the fractal. Everything works fine until I start changing the slider. When I change it, the fractal animation becomes choppy, and eventually the "drawFractal" function stops being called.
Here is the slider HTML:
<input type="range" id="qualitySlider" min="1" max="10"></input>
Here is the javascript (it just generates a fractal):
var count = 0.5;
var slider = document.getElementById("qualitySlider");
var g = document.getElementById("drawingCanvas").getContext("2d");
function drawFractal() {
var cellSize = Math.ceil(slider.value);
//canvas is 700 by 400
g.fillStyle = "black";
g.clearRect(0, 0, 700, 400);
//Eveything from here to the end of this function generates the fractal
var imagC = Math.cos(count)*0.8;
var realC = Math.sin(count)*0.5;
for (x = 0; x < 700; x+=cellSize) {
for (y = 0; y < 400; y+=cellSize) {
var yCoord = (x / 700.0 - 0.5)*3;
var xCoord = (y / 400.0 - 0.5)*3;
var real = xCoord;
var imag = yCoord;
var broken = 0;
for (i = 0; i < 8; i++) {
var temp = real*real - imag*imag + realC;
imag = 2*imag*real + imagC;
real = temp;
if (real*real + imag*imag >= 4) {
broken = true;
break;
}
}
if (!broken) {
g.fillRect(x, y, cellSize, cellSize);
}
}
}
count = count + 0.04;
}
setInterval(drawFractal, 60);
I just need the "drawFractal" function to be called reliably every 60 milliseconds.
This is my improved code. I just used requestAnimationFrame to recursively call the "drawFractal" function. I also restricted the animation to 24 frames/sec with the setTimeout function.
var count = 0.5;
var qualitySlider = document.getElementById("qualitySlider");
var g = document.getElementById("drawingCanvas").getContext("2d");
function drawFractal() {
var cellSize = Math.ceil(qualitySlider.value);
//canvas is 700 by 400
g.fillStyle = "black";
g.clearRect(0, 0, 700, 400);
var imagC = Math.cos(count)*0.8;
var realC = Math.sin(count)*0.5;
for (x = 0; x < 700; x+=cellSize) {
for (y = 0; y < 400; y+=cellSize) {
var yCoord = (x / 700.0 - 0.5)*3;
var xCoord = (y / 400.0 - 0.5)*3;
var real = xCoord;
var imag = yCoord;
var broken = 0;
for (i = 0; i < 8; i++) {
var temp = real*real - imag*imag + realC;
imag = 2*imag*real + imagC;
real = temp;
if (real*real + imag*imag >= 4) {
broken = true;
break;
}
}
if (!broken) {
g.fillRect(x, y, cellSize, cellSize);
}
}
}
count = count + 0.04;
setTimeout(function() {
requestAnimationFrame(drawFractal);
}, 41);
}
drawFractal();
You are using setInterval() to call drawFractal every 60 ms, and then every time drawFractal is executed, you're calling setInterval() again, which is unnecessary. You now have two timers attempting to draw fractals every 60 ms... then you'll have 4, then 8, etc.
You need to either (1) call setInterval() once at the start of program execution and not call it again, or (2) switch to using setTimeout(), and call it at the end of each drawFractal().
I'd use the second option, just in case your fractal ever takes more than 60 ms to draw.

Use JS countdown timer in Canvas with requestAnimationFrame

I'm making a simple canvas- game and I'm using requestAnimationFrame (Paul Irish's version) for the gameloop. I'm using a javascript countdown in the game (showing seconds and 100th of seconds), but it doesn't count down in the right speed. I know this has to do with the refresh- rate of the game, so that the counter updates every frame instead of every 100th second. How do I fix this?
Here's the timer object:
/**
* The timer as an object
*/
function Timer(position, time) {
this.position = position || new Vector(150,210);
this.time = time || 6000;
}
Timer.prototype = {
start: function() {
console.log('start running');
this.initial = this.time; //time in 100'ths of seconds
this.count = this.initial;
this.counter; //10 will run it every 100th of a second
clearInterval(this.counter);
//this.counter = setInterval(this.timer, 10);
this.timer();
},
timer: function() {
//console.log(this.count);
this.count--;
var res = this.count / 100;
//Show counter in canvas
return 'Tid kvar: ' + res.toPrecision(this.count.toString().length) + ' sekunder';
},
draw: function(ct) {
if(this.initial === undefined){this.start();} //Start the timer
ct.save();
if(this.count <=0){ //Remove timer if timer has reached 0
ct.clearRect(this.position.x, this.position.y, Racetrack.innerTrackWidth, Racetrack.innerTrackHeight);
return false;
} else { //draw timer
ct.save();
ct.font = 'bold 3em arial';
ct.fillStyle = 'orange';
ct.fillText(this.timer(), this.position.x, this.position.y);
}
ct.restore();
},
}
And the gameloop and the part calling the timer:
var init = function(canvas) {
timer = new Timer(new Vector(160,210), 3000);
}
var render = function() {
ct.clearRect(0,0,width,height);
ship.draw(ct);
racetrack.draw(ct);
//draw timer or results if timer reached 0
timer.draw(ct) !=false ? timer.draw(ct) : endFrame.draw(ct);
};
var gameLoop = function() {
var now = Date.now();
td = (now - (lastGameTick || now)) / 1000; // Timediff since last frame / gametick
lastGameTick = now;
if(timer.draw(ct) == false) { //stop the game if timer has reached 0.
cancelRequestAnimFrame(gameLoop);
console.log('Time\'s up!');
} else { //Otherwise, keep the game going.
requestAnimFrame(gameLoop);
}
update(td);
render();
};
I've also tried this as timer- object, but debugging this.count shows number the first loop, undefined the second loop and NaN every loop after that (And I'm not sure this will fix the timing issue either way?):
function Timer(position, time) {
this.position = position || new Vector(150,210);
this.time = time || 6000;
}
Timer.prototype = {
start: function() {
console.log('start running');
this.initial = this.time; //time in 100'ths of seconds
this.count = this.initial;
this.counter; //10 will run it every 100th of a second
clearInterval(this.counter);
this.counter = setInterval(this.timer, 10);
this.timer();
},
timer: function() {
console.log(this.count);
this.count--;
},
getTime: function() {
var res = this.count / 100;
return 'Tid kvar: ' + res.toPrecision(this.count.toString().length) + ' sekunder';
},
draw: function(ct) {
if(this.initial === undefined){this.start();} //Start the timer
ct.save();
if(this.count <=0){ //Remove timer if timer has reached 0
ct.clearRect(this.position.x, this.position.y, Racetrack.innerTrackWidth, Racetrack.innerTrackHeight);
return false;
} else { //draw timer
ct.save();
ct.font = 'bold 3em arial';
ct.fillStyle = 'orange';
ct.fillText(this.getTime(), this.position.x, this.position.y);
}
ct.restore();
},
}
Not sure if you are asking to display the time in 1/100 intervals or if the time is inaccurate when using setInterval.
A: setInterval should not be used for timing, as it is far from accurate and gets worse the smaller the interval, and even worse if you have animations running.
B: Browser's refresh at 1/60th of a second. You can force the display to render and present at 1/100 of a second but then depending on the graphics hardware it may never be displayed because the display is scanning pixels elsewhere on the screen at the time. Or you will get shearing when you overwrite the display just as the graphics is being written to the display.
Best way to get a countdown using requestAnimationFrame
var start = true; // flags that you want the countdown to start
var stopIn = 3000; // how long the timer should run
var stopTime = 0; // used to hold the stop time
var stop = false; // flag to indicate that stop time has been reached
var timeTillStop = 0; // holds the display time
// main update function
function update(timer){
if(start){ // do we need to start the timer
stopTime = timer + stopIn; // yes the set the stoptime
start = false; // clear the start flag
}else{ // waiting for stop
if(timer >= stopTime){ // has stop time been reached?
stop = true; // yes the flag to stop
}
}
timeTillStop = stopTime - timer; // for display of time till stop
// log() should be whatever you use to display the time.
log(Math.floor(timeTillStop / 10) ); // to display in 1/100th seconds
if(!stop){
requestAnimationFrame(update); // continue animation until stop
}
}
requestAnimationFrame(update); // start the animation
Forgot to add.
You are never going to get 100th of a second accuracy via that method. You will be on average 7-8 ms out. But unless you make it far more complex that is the best you can do. That 7-8ms average is constant and the same for 2 hours and 1 second and is just determined by the animation refresh time of about 16 odd ms.

Counting how many seconds a user dragging the mouse

I have a function where the user sort of "scratching" a surface when he drags the mouse over it, reviling content underneath this surface. I want to count how many seconds he dragged the mouse, and when he reaches 5 seconds - do something. If he lets go after, for example, 3 seconds, the count should stop and resume from 3d second when he resumes dragging. I tried to do it with setInterval and add 1 to seconds counter every 1000 ms, but no matter what I've tried - I'm either getting some crazy numbers in the counter or it just stays on 0. Here's my code:
var interval, info;
var totalSeconds = 0;
function init()
{
...more vars declaration and initialization
function scratchOff(x, y)
{
mainctx.save();
mainctx.beginPath();
mainctx.arc(x,y,radius,0,Math.PI*2,false);
mainctx.clip();
mainctx.drawImage(bottomImage, 0, 0);
mainctx.restore();
}
$('#overlay').mousedown(function(e){
isMouseDown = true;
var relX = e.pageX;
var relY = e.pageY;
scratchOff(relX, relY, true);
});
$('#overlay').mousemove(function(e){
var relX = e.pageX;
var relY = e.pageY;
overlayctx.clearRect(0,0,canvasWidth,canvasHeight);
overlayctx.drawImage(coinImage, relX-radius, relY-radius);
if (isMouseDown) {
scratchOff(relX, relY, false);
countSeconds(); // - THIS CALLS FOR THE FUNCTION THAT IS SUPPOSED
// TO COUNT SECONDS ONCE THE USER STARTS DRAGGING THE MOUSE
}
});
$('#overlay').mouseup(function(e){
isMouseDown = false;
clearInterval(interval);
});
var mainctx = $('canvas')[0].getContext('2d');
var radius = 10;
topImage.onload = function(){
mainctx.drawImage(topImage, 0, 0);
};
topImage.src = "images/oie_canvas.png";
}
// THIS IS THE FUNCTION THAT'S SUPPOSED TO COUNT SECONDS
function countSeconds() {
interval = setInterval(function(){
totalSeconds = totalSeconds++;
info.innerHTML = totalSeconds;
if(totalSeconds >= 5) clearInterval(interval);
}, 1000);
}
How can I make it work?
You would start a timer of 1 sec, which monitor your isMouseDown flag and start counting, when you reach 5 sec you go for your action, no need to call the countSeconds every time.
Here is an example:
EDITED
$(function() {
setInterval(function(){
if(isMouseDown) {
totalSeconds++;
}
if(totalSeconds== 5) {
//do your action;
totalSeconds = 0;
}
info.innerHTML = totalSeconds;
}, 1000);
} );
var isMouseDown = false;
function init()
{
... your code
}
EDITED 2
Full Example
var topImage = new Image();
var bottomImage = new Image();
var coinImage = new Image();
bottomImage.src = "http://i58.tinypic.com/2i093ia.jpg";
coinImage.src = "http://i61.tinypic.com/30acmtt.png";
var info;
var interval;
var totalSeconds = 0;
$(function() {
setInterval(function(){
if(isMouseDown) {
totalSeconds++;
}
if(totalSeconds== 5) {
alert("5 sec");
totalSeconds = 0;
}
info.innerHTML = totalSeconds;
}, 1000);
} );
var isMouseDown = false;
function init()
{
var canvasWidth = $('#myCanvas').width();
var canvasHeight = $('#myCanvas').height();
$('body').append('<canvas id="overlay" width="'+canvasWidth+'" height="'+canvasHeight+'" />');
var overlayctx = $('canvas')[1].getContext('2d');
overlayctx.drawImage(coinImage, 0,0);
info = document.getElementById('info');
function scratchOff(x, y)
{
mainctx.save();
mainctx.beginPath();
mainctx.arc(x,y,radius,0,Math.PI*2,false);
mainctx.clip();
mainctx.drawImage(bottomImage, 0, 0);
mainctx.restore();
}
$('#overlay').mousedown(function(e){
isMouseDown = true;
var relX = e.pageX;
var relY = e.pageY;
scratchOff(relX, relY, true);
});
$('#overlay').mousemove(function(e){
var relX = e.pageX;
var relY = e.pageY;
overlayctx.clearRect(0,0,canvasWidth,canvasHeight);
overlayctx.drawImage(coinImage, relX-radius, relY-radius);
if (isMouseDown) {
scratchOff(relX, relY, false);
countSeconds();
}
});
$('#overlay').mouseup(function(e){
isMouseDown = false;
clearInterval(interval);
});
var mainctx = $('canvas')[0].getContext('2d');
var radius = 10;
topImage.onload = function(){
mainctx.drawImage(topImage, 0, 0);
};
topImage.src = "http://i61.tinypic.com/xpzbx0.png";
}

Categories