Calling renderer.render() in a requestAnimationFrame() loop will often fire faster than the <canvas> element can update its image, making any "true" FPS measurement inaccurate.
In three.js, how can I block each iteration long enough to let the <canvas> update its image?
For example, let's say you have a laggy three.js scene where you are also displaying the frame number in an HTML element on your page. If you record your screen at a high capture rate (~120Hz), you will notice that the frame number displayed in the HTML element will "skip" a frame or two occasionally, which artificially inflates FPS measurements. For example, between only two unique images in the canvas, the frame number would go from 1,340 to 1,343, essentially skipping 2 frames.
It appears that my requestAnimationFrame() loop is firing faster than the browser can repaint the <canvas> and all the other elements on the page. I have already tried all sorts of "hacks", including the following:
- 🤔 Connecting the
<canvas>to a<video>element to take advantage ofvideo.requestVideoFrameCallback(callback). This is the only one that stopped the skipped frames, but it introduced a much worse lag that outweighed the benefits. - ❌ Doing this (using
MessageChannel): https://webperf.tips/tip/measuring-paint-time (did nothing) - ❌ Doing this:
requestAnimationFrame(() => requestAnimationFrame(callback))(did nothing)
So is there any way I can reliably block each iteration of requestAnimationFrame() to give the canvas (and all the elements on the page) enough time to repaint? This would allow me to get a "true" FPS measurement of the actual frames that were visible on screen.
Here is a JSFiddle demonstrating a laggy scene that prints out the FPS based on a requestAnimationFrame() loop. Just increase the number of instances for this InstancedMesh on this line to something that starts lagging your scene:
const mesh = new THREE.InstancedMesh(geometry, material, 100);
Next, record your screen at a high capture rate and rapidly rotate the camera around. You will notice the frame number skips a frame occasionally.
Here is a small recording of the frame number. Notice how frames 1022 and 1029 are skipped (Chrome was used to test):

InstancedMeshto something that would begin lagging the scene (your machine may differ than mine). Next, begin recording the scene at a high capture rate (preferably 120Hz or greater). Finally, jerk the camera around rapidly as you record the scene for a few seconds and stop the recording. You can now manually step through each frame and see that frames get skipped occasionally. Note: I tested this using Chrome.