4

My JS code:

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

var mouse = {x:0,y:0}

const times = [];
let fps;

function refreshLoop() {
  window.requestAnimationFrame(() => {
    const now = performance.now();
    while (times.length > 0 && times[0] <= now - 1000) {
      times.shift();
    }
    times.push(now);
    fps = times.length;
    refreshLoop();
  });
}

refreshLoop();

function draw() {
  ctx.fillStyle = "black"
  ctx.fillRect(0, 0, c.width, c.height);
  ctx.strokeStyle = "white"
  ctx.beginPath();
  var e = window.event;
  ctx.arc(mouse.x, mouse.y, 40, 0, 2*Math.PI);
  ctx.stroke();
  ctx.font = "30px Comic Sans MS";
  ctx.fillStyle = "red";
  ctx.textAlign = "center";
  ctx.fillText(fps, c.width/2, c.height/2); 
}

setInterval(draw, 0);

document.addEventListener('mousemove', function(event){
  mouse = { x: event.clientX, y: event.clientY }
})

My HTML is just the canvas declaration.

To my understanding, setinterval(x, 0) is supposed to run as fast as possible but it's never exceeding 60fps. I'm trying to hit 240+ fps to reduce input lag.

  • Have you seen this answer, does it help?: https://stackoverflow.com/questions/32947439/how-do-i-program-frames-per-second I am not too familiar with HTML canvas, but my experience gaming would make me think that increasing the FPS is not going to reduce input lag (I could be wrong). I would also imagine trying to get the browser to render at a higher FPS would have a performance hit. It may also be worth looking in the Game Dev stack exchange for answers. – Ginger Squirrel May 31 '18 at 10:49
  • What browser(s) are you targetting to expect a rendering faster than 60 fps? – Guillaume Georges May 31 '18 at 10:50
  • @GingerSquirrel No, I hadn't seen that. Thank you, I will have a look. – Geoff Clements May 31 '18 at 11:58
  • @RogerC Chrome and Firefox on desktop. – Geoff Clements May 31 '18 at 11:58
  • [Here](https://www.youtube.com/watch?v=cCOL7MC4Pl0) is a helpful video on the difference between `setTimeout` and `requestAnimationFrame` and when to use each. – TheCrzyMan May 31 '18 at 13:10
  • @TheCrzyMan Thanks that was very helpful. – Geoff Clements May 31 '18 at 16:57

1 Answers1

5

First, never use setInterval(fn, lessThan10). There is a great possibility that fn will take more than this time to execute, and you may end up stacking a lot of fn calls with no interval at all, which can result* in the same as the well known while(true) browser crasher®.

*Ok, in correct implementations, that shouldn't happen, but you know...


Now, to your question...

Your code is quite flawn.

You are actually running two different loops concurrently, which will not be called at the same interval.

  • You are checking the fps in a requestAnimationFrame loop, which will be set at the same frequency than your Browser's painting rate (generally 60*fps*).
  • You are drawing in the setInterval(fn, 0) Your two loops are not linked and thus, what you are measuring in the first one is not the rate at which your draw is called.

It's a bit like if you did

setInterval(checkRate, 16.6);
setInterval(thefuncIWantToMeasure, 0);

Obviously, your checkRate will not measure thefuncIWantToMeasure correctly

So just to show that a setTimeout(fn, 0) loop will fire at higher rate:

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

var mouse = {
  x: 0,
  y: 0
}

const times = [];
let fps;
draw();

function draw() {
  const now = performance.now();
  while (times.length > 0 && times[0] <= now - 1000) {
    times.shift();
  }
  times.push(now);
  fps = times.length;

  ctx.fillStyle = "black"
  ctx.fillRect(0, 0, c.width, c.height);
  ctx.strokeStyle = "white"
  ctx.beginPath();
  ctx.arc(mouse.x, mouse.y, 40, 0, 2 * Math.PI);
  ctx.stroke();
  ctx.font = "30px Comic Sans MS";
  ctx.fillStyle = "red";
  ctx.textAlign = "center";
  ctx.fillText(fps, c.width / 2, c.height / 2);
  setTimeout(draw, 0);
}
<canvas id="myCanvas"></canvas>

Now, even if a nested setTimeout loop is better than setInterval, what you are doing is a visual animation.

It makes no sense to draw this visual animation faster than the browser's painting rate, because what you will have drawn on this canvas won't be painted to screen.

And as said previously, that's exactly the rate at which an requestAnimationFrame loop will fire. So use this method for all your visual animations (At least if it has to be painted to screen, for some rare case there are other methods I could link you to in comments if needed).

Now to solve your actual problem, which is not to render at higher rate, but to handle user's inputs at such rate, then the solution is to split your code.

  • Keep your drawing part bound to a requestAniamtionFrame loop, doesn't need to get faster.
  • Update your object's values that should respond to user's gesture synchronously from user's input. Though, beware some user's gestures actually fire at very high rate (e.g WheelEvent, or window's resize Event). Generally, you don't need to get all the values of such events, so you might want to bind these in rAF throttlers instead.
  • If you need to do collision detection with moving objects, then perform the Math that will update moving objects from inside the user's gesture, but don't draw it on screen.
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Wow, thank you for the in-depth answer! I guess that'll teach me to copy paste code when I'm not sure how it works. Pretty funny my fps check was working on its own ignoring what I thought it was measuring. – Geoff Clements May 31 '18 at 13:19