I've written some code in JS that allows the user to click on the screen to create a square, and hold down the mouse to increase the size of the square before stamping it. The problem is that the speed at which the size of the square increases while the mouse is held down slows once the mouse is moved from the originally clicked position. I'm using intervals to change the size over a time of 1 millisecond. Here is the JS code (with Jquery):
UPDATE: If you run the code snippet you won't see the issue. Try saving it as a file and running it, then the issue will probably occur.
<!DOCTYPE html>
<html>
<head>
<title>My Canvas</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
var mouseDown
var c = document.getElementById('myCanvas');
var ctx = c.getContext("2d");
var objects = []
c.addEventListener("mousedown", onMouseDown);
c.addEventListener("mouseup", onMouseUp);
function createSquare(x, y, size) {
this.x = x;
this.y = y;
this.size = size;
this.draw = draw;
this.drawStylus = drawStylus;
this.clear = clear;
};
function draw() {
ctx.fillRect(this.x, this.y, this.size, this.size);
};
function drawStylus() {
ctx.fillRect(this.x, this.y, this.size, this.size);
};
function clear() {
ctx.clearRect(this.x, this.y, this.size, this.size);
}
// var mySquare = new createSquare(100,100,100);
// mySquare.draw();
function onMouseDown() {
mouseDown = true
x = event.clientX
y = event.clientY
size = 100
console.log('clicked')
interval = setInterval(function () {
size = size + 5
var cursorSquare = new createSquare(x,y,size);
cursorSquare.clear();
cursorSquare.drawStylus();
}, 1);
};
function onMouseUp() {
console.log('up')
if (mouseDown) {
clearInterval(interval);
var newSquare = new createSquare(x,y,size);
newSquare.draw();
mouseDown = false
};
};
});
</script>
</head>
<body>
<canvas id='myCanvas' width="5000" height="5000" style="border:1px solid #000000;">
</canvas>
</body>
</html>
The problem is the interval,
First. 1 millisecond is too short a time for any rendering, the mouse is only updated around 1/60th of a second, and rendering above 1/60th (shorter period) will put an overly heavy load on the browser and as the screen refresh is also at 1/60th of a second most of the rendering will be lost before it even gets a chance to make it to the the display.
Secondly, though 1 millisecond is too short a time for some browsers (forget the min interval time for each browser) the problem is that when you present a task that is longer than the interval period the browser still keeps adding the function calls to the call stack, effectively delaying other events and browser upkeep. Continued use of setInterval in such a situation will result in a call stack overflow, crashing all javascript on the page.
The simple rule is NEVER use setInterval for any reason. Use setTimeout instead, as it protects from call stack overflows and stops uncontrolled calls being placed on the call stack. Using setTimeout can actually result in better timed intervals.
// simple timeout
var interval = 50
function myTimer(){
setTimeout(myTimer,interval); // set up the next interval
}
// start it
setTimeout(myTimer,interval);
By getting the time via new Date().valueOf() you can adjust the timeout for the next call to stay as close as possible to the required interval, something that setInterval makes very hard to do. To stop the calls simply don't call setTimeout if a stop flag is set.
But all that said for graphics you should be using requestAnimationFrame which is very similar to setTimeout but you do not provide the timeout, the browser will get the best time to keep you in sync with the rendering and layout engines and also with the display hardware so you dont get flickering and or shearing.
function update(time){ // requestAnimationFrame calls the function with the time in milliseconds
// do what is needed
requestAnimationFrame(update); // get next frame when browser is ready 1/60th second
}
requestAnimationFrame(update); // start it going
For coordinating with a mouse just have your mouse listeners set some variables like mouseX,mouseY and buttonState and do nothing more. In the update function use these values to handle the mouse. This produces a much more manageable mouse interface.
Related
I'm newer to js coding and I can't find any answers to my question. I'm working with the tag in html and trying to get my JS code to redraw the canvas when I want. I've tried using a simple while loop to do it, but that just goes through the while loop then draws the end result without showing the updates in between. My current code (below is using the setTimeout/clearTimeout calls, but it produces the same result. Not sure if this is even possible. Maybe it's just how I'm updating my canvas?
This is my code for calling my function to update my arrays I'm storing my data in and then update the canvas.
var done = false;
var count = 0;
while (!done) {
timer = setTimeout(updateGrid(grid), 500);
clearTimeout(timer);
done = stop(count, allDone);
count++;
}
The functions I'm using to draw on the canvas:
context = canvas.getContext("2d");
context.fillStyle = color;
context.fillRect(x, y, w, h);
Is there something I'm missing?
clearTimeout is nullifying your updateGrid timeout.
I think what you want is to use setInterval instead. But you can also use setTimeout and adjust the timeout time (500 * (i + 1) or something) and set up all your timeouts like that
while (!done) {
timer = setTimeout(updateGrid(grid), 500);
clearTimeout(timer); //this is nullifying the line above
done = stop(count, allDone);
count++;
}
Need help with Canvas refresh, very glitchy and flashing, and im using setInterval. Dont know how to use requestanimation frame, any help is great
//initiates entire game
function fullLoader() {
setInterval(drawPoke, fps);
setInterval(drawHaunt, fps);
setInterval(drawChar, fps);
setInterval(drawDusk, fps);
setInterval(refresh, 1000/30);
}
function refresh() {
con.clearRect(0, 0, canvas.width, canvas.height)
}
The function are drawings or png images and are set to relaod at the interval of fps which is set to 30.
Never..ever.. use setInterval for animation!
Use requestAnimationFrame as it is synced to the display rate and ensures that changes you make to the DOM (canvas) do not get moved to the display until the display hardware is in between frames.
function fullLoader() {
requestAnimationFrame(mainLoop); // get first frame
}
// This mainLoop is called every 1/60th second (if you return in under 1/60th second
// if the render time is loonger than 1/60 then you will have to use
// time to keep the speed at 1/30th second. Search SO for how.
function mainLoop(time){ // time is given by browser, in ms 1/1000
// accurate to 1/1,000,000
refresh();
drawPoke();
drawHaunt();
drawChar()
drawDusk();
requestAnimationFrame(mainLoop); // get next frame
}
function refresh() {
con.clearRect(0, 0, canvas.width, canvas.height);
}
No matter what I do, I can't get a fluent canvas animation under Firefox. I even set up a simple test code which does absolutely nothing except calling empty draw function every 1/40 s and it's still flickering.
var t = 0;
function draw(time)
{
console.log(Math.round(time - t));
t = time;
}
setInterval(function(){ requestAnimationFrame(draw); }, 25);
Delay between frames under Firefox sometimes jumps to over 150 ms which is easily noticable by human eye. Same thing happens when using simple setInterval to call draw(), without the requestAnimationFrame. It runs perfectly under Chrome and Opera.
I've also tried getting rid of setInterval, results are the same:
var t = 0;
function draw(time)
{
requestAnimationFrame(draw);
console.log(Math.round(time - t));
t = time;
}
draw();
Is it a known issue? Is there any way to work around it?
Turns out the current implementation of requestAnimationFrame under Firefox is terrible and fails to provide smooth animation when called from timers or network events (even those which are repeated at constant interval).
This makes it hard to redraw canvas when state is updated over websocket connection. The only way I could get smooth animation was calling requestAnimationFrame immediately:
(function draw()
{
requestAnimationFrame(draw);
// do something
})();
When using this method, it's quite often a good idea to implement some kind of frame interpolation, as draw() calls won't be synchronized with network events.
I am currently working on a html5 canvas game. I want to add a timer, so that you have a certain amount of time to collect as many items as possible in. I have tried a few ways of doing it, but due to my lack of skill and experience when it comes to javascript I have not yet made it work. So my question is how to do this in an as simple as possible way?
My code:
Thanks in advance!!
requestAnimationFrame is a very efficient way of doing timers in the browser.
Some Benefits of requestAnimationFrame:
automatically synchronizes canvas drawings with the current display refresh cycle,
multiple requestAnimationFrame calls are coordinated,
automatically suspends the loop if the user changes do a different browser tab
each loop automatically receives a highly precise timestamp. Use this timestamp to determine when to trigger animation work (like your end-of-game) and/or to determine how much animation work to do.
Here's how to make it work:
Create 1+ javascript objects. Each object is one timer.
Each timer object defines some work that should be done after a specified delay.
var timers=[];
timers.push({
// this timer fires every 500ms
delay:500,
// fire this timer when requestAnimationFrame's timestamp
// reaches nextFireTime
nextFireTime:0,
// What does this timer do?
// It execute the 'doTimers' function when this timer fires
doFunction:doTimers,
// just for testing: accumulate how many times this timer has fired
counter:0
});
Create an animation loop with requestAnimationFrame
// the animation loop
// The loop automatically receives the currentTime
function timerLoop(currentTime){
// schedule another frame
// this is required to make the loop continue
// (without another requestAnimationFrame the loop stops)
requestAnimationFrame(timerLoop);
// iterate through each timer object
for(var i=0;i<timers.length;i++){
// if the currentTime > this timer's nextFireTime...
// then do the work specified by this timer
if(currentTime>timers[i].nextFireTime){
var t=timers[i];
// increment nextFireTime
t.nextFireTime=currentTime+t.delay;
// do the work specified in this timer
// This timer will call 'doFunction' & send arguments: t,i
t.doFunction(t,i);
}
}
}
Start the animation loop
requestAnimationFrame(timerLoop);
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var timers=[];
timers.push({delay:50,nextFireTime:0,doFunction:doTimers,counter:0});
timers.push({delay:500,nextFireTime:0,doFunction:doTimers,counter:0});
timers.push({delay:5000,nextFireTime:0,doFunction:doTimers,counter:0});
requestAnimationFrame(timerLoop);
function timerLoop(currentTime){
requestAnimationFrame(timerLoop);
for(var i=0;i<timers.length;i++){
if(currentTime>timers[i].nextFireTime){
var t=timers[i];
t.nextFireTime=currentTime+t.delay;
t.doFunction(t,i);
}
}
}
function doTimers(t,i){
ctx.clearRect(0,100+i*20-20,cw,20);
ctx.fillText('Timer#'+i+' with '+t.delay+'ms delay has fired '+(++t.counter)+' times.',20,100+20*i);
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>
When the game begins set a timer using the setTimeout() function.
Since your game is currently running indefinitely I'd change the last bit of your code to give it an ending.
var time = Date.now();
var running = setInterval(run, 10); // Save this so we can clear/cancel it later
setTimeout(function() { // Set a timer
clearInterval(running); // Stop the running loop
alert('Game over!'); // Let the user know, do other stuff here
}, 30000); // time in miliseconds before the game ends
I'm wanting to animate an element using setInterval. I've put my code into an object with 2 functions one to initialize and one to animate using setInterval. When I try to run the code the animation works once then causes the browser to hang. The only thing I can think of is an infinite loop being created somewhere however I can't see anywhere in my code that would cause this.
What is causing the browser to crash, how can this be overcome ?
<div id='box' style='position:absolute;height:100px;width:100px;background-color:#44e'>box</div>
<script>
var box = {
init : function(elemId) {
box.elem = document.getElementById(elemId);
box.timer = setInterval;
box.tick = 0;
box.animate();
},
animate: function() {
if(box.tick < 100) {
box.elem.style.top = box.tick +'px';
box.elem.style.left = box.tick +'px';
box.tick++;
} else {
clearInterval(box.timer);
}
var timer = setInterval(box.animate, 50)
}
}
box.init('box');
</script>
setInterval sets up a function that will be called repeatedly by the browser until you cancel the interval timer. Your code isn't doing that, because the only call to clearInterval is using box.timer, which is never set to a timer handle (the return value from setInterval). So you end up scheduling thousands of calls (a new series every time animate is called) and bringing the browser to its kneeds.
At the very least, this:
var timer = setInterval(box.animate, 50)
should probably be:
box.timer = setInterval(box.animate, 50);
Or you may want setTimeout (which schedules only one call back).