- Use `requestAnimationFrame`, don't use timers. JS timers are really bad, and they won't sync with monitor refresh rates.
- If possible, avoid lots of canvas calls. For example, if you want to draw something simple like a bunch of boxes on the screen, grab the pixel array from the canvas and manipulate it directly, don't call `ctx.rect` in a for loop.
I once stuck in a quick dozen-line block of code before a demo that just drew a couple hundred tiny pixel stars in the background and occasionally randomized their position. Went back and profiled later and found out it was taking something like 30% of my render time each frame, and refactoring just that one block of code got rid of a bit of stuttering on old hardware.
- On that note, cache stuff in memory. My game is tile-based. At the start of a level, I iterate over the tiles on an offscreen canvas and generate a single, giant image for the entire map. Then for subsequent frames I can render that with a single sprite call, rather than by iterating over every tile.
Caching visual effects is huge. In general, don't be afraid to stick generated effects in RAM, because every call you make to Canvas has an overhead.
- Also on that note, don't be afraid to use multiple canvases. If you have an overlay or a visual effect on top of your main game, and you can just update that without needing to redraw all of your sprites/tiles, that can sometimes be a big performance benefit.
Thanks! Great tips. Do you write about this stuff elsewhere?
However, assuming the cap is an issue, I still strongly doubt that with `setInterval` you'll be able to get the precision you need to target specific monitor refresh rates, even if you can figure out a way to detect what the refresh rate is. Maybe if you're targeting Electron, but even there I kind of doubt it. Even with frame caps, `requestAnimationFrame` is still likely the best bet (opinion me).
The reality of games on the web is that timing in general, whether it be for update logic, rendering, or audio is kind of a massive pain, and you'll always be accepting some compromises. To me, those compromises aren't big enough to outweigh some of the other substantial benefits, but I don't want to pretend those compromises don't exist.
So my gaming monitor running 144hz at 1080p runs the application at 144 FPS according to my FPS counter from stats.js[0]
On my laptop running at 60hz it's capped at 60 FPS. There is a huge difference in the "smooth-ness" as well as reduced input lag on the 144hz vs 60hz monitors. So much that I currently dislike working on the app on <100hz monitors.
> If possible, avoid lots of canvas calls. For example, if you want to draw something simple like a bunch of boxes on the screen, grab the pixel array from the canvas and manipulate it directly, don't call `ctx.rect` in a for loop.
I think this is likely what's causing me the most trouble. The problem is that my game needs to draw many hundreds of circles, each of different colors/sizes. Perhaps I need to pre-render these to an off-screen canvas and then do some blitting.
I'm also tempted now to try what someone else suggested and target WebGL instead (likely via Pixi.js).
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/...
If you plan on having any amount of medium to large numbers of things (heck I’d say any amount) you’d be doing it wrong to be using the 2D canvas API.