Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Render canvas faster than 60 times per second?

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.

like image 454
Geoff Clements Avatar asked Oct 14 '25 14:10

Geoff Clements


1 Answers

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.
like image 131
Kaiido Avatar answered Oct 17 '25 02:10

Kaiido