All articles
Rendering 2026-04-28 · 8 min read

Staying sharp on every screen without changing the game’s coordinates

A canvas has two sizes: the one it is displayed at and the one it is drawn at. Confusing them is why so many browser games look soft.

Ask a canvas to fill its container and it will, but the result is usually blurred — because the canvas has been stretched like an image rather than rendered at the resolution it is being displayed at. The cause is a distinction that is easy to miss and easy to fix.

Two sizes, one element

A canvas has a CSS size, which is the space it occupies in the layout, and a backing-store size, which is the number of pixels it actually contains. If the backing store is smaller than the display size, the browser upscales it and the result is soft. On a display with a device pixel ratio of three — normal on phones — a canvas styled to fill the screen but left at its default backing size is being stretched threefold.

Scale the context, not the coordinates

The fix is to set the backing store to the CSS size multiplied by the device pixel ratio, and then scale the drawing context by that same ratio. After that, all game logic can work in ordinary CSS pixels: a collision radius of twenty means twenty, a button is laid out in the same units as the page, and none of the game code needs to know the screen density at all.

The reason to scale rather than multiply coordinates by hand is that hand-scaling has to be applied consistently — to drawing, to hit testing, to sizes — and any single omission produces a bug that only appears on high-density screens, where it is hardest to notice during development.

Resize means re-layout

Rotating a phone or resizing a window changes the space available, and merely re-stretching the canvas is not enough: the play area has to be recomputed so that the game still fits and nothing important drifts off the edge. The games here recalculate their layout on resize and re-derive any positions that were expressed relative to the old dimensions, which keeps a game playable through a rotation rather than leaving it half cut off.

Resize events can also arrive in bursts while a window is being dragged, so the handling is kept cheap and idempotent.

The cost of too much sharpness

Rendering at the device pixel ratio is the correct default, and it is not free: a ratio of three means nine times the pixels of the CSS size. On a large, high-density display that is a lot of fill rate for a game that does not need it, which is why the frame budget has to be checked on real hardware rather than assumed.

Where a game is fill-rate bound, capping the ratio — rendering at two rather than three — is a reasonable trade that is visually almost indistinguishable and materially cheaper. What is not a reasonable trade is leaving the canvas at its default size and accepting a permanently blurred game to save the cost, because the blur is the first thing a player notices.

Want to go deeper?

Building something for the browser? Send us a link and we will tell you honestly whether it fits.

Talk to us