Getting the HTML5 game canvas size right on mobile comes down to one fact: a canvas has two sizes. Its CSS size is how big it looks on the page; its drawing buffer (canvas.width and canvas.height) is how many pixels you actually draw. On a phone those must differ, or the game looks blurry, stretched or cut off. This guide covers the buffer, the base resolution to design for, the scale modes, responsive layout, how to stop scaling you don't want, and fullscreen on Android and iPhone.
It's one part of our HTML5 mobile game development guide, which covers touch, audio and performance as well.
Canvas size on mobile: CSS pixels vs device pixels
Phones report a small screen in CSS pixels and draw it with many more physical pixels. The ratio between the two is window.devicePixelRatio. We test every game on Playwright's emulated phone profiles, and their numbers make the point:
| Profile | Visible viewport (CSS px) | Pixel ratio | Buffer at full ratio | Buffer with ratio capped at 2 |
|---|---|---|---|---|
| Pixel 7 (Chromium) | 412 × 839 | 2.625 | 1082 × 2202 | 824 × 1678 |
| iPhone 15 (WebKit) | 393 × 659 | 3 | 1179 × 1977 | 786 × 1318 |
The iPhone 15 profile's screen is 393 × 852, but Safari's bars take part of it, so the game gets 393 × 659. That gap is why you size to the visible viewport, never to screen.width.
The fix for blur is to size the buffer in device pixels and draw in CSS pixels:
const MAX_DPR = 2;
function resize() {
const w = window.innerWidth, h = window.innerHeight;
const dpr = Math.min(window.devicePixelRatio || 1, MAX_DPR);
canvas.style.width = `${w}px`;
canvas.style.height = `${h}px`;
canvas.width = Math.round(w * dpr);
canvas.height = Math.round(h * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0); // 1 unit = 1 CSS pixel from here on
layout(w, h);
}
window.addEventListener("resize", resize);
resize();Capping at 2 is a deliberate trade. In the table above, the capped buffer has 58% of the Pixel 7's full-ratio pixels and 44% of the iPhone's, which is a large saving in fill rate for a difference few players notice in a moving game. A low-quality setting can drop the ratio to 1 on weak phones. WebGL games do the same thing through the renderer: Three.js has renderer.setPixelRatio(Math.min(devicePixelRatio, 2)), and Phaser lets you size the game in device pixels with a matching zoom.
How many pixels should my HTML5 game be?
There's no single right size, because phones don't share one shape. Pick a base resolution with your target aspect ratio, build the game in those units, and scale it to the screen:
| Orientation | Base resolution | Aspect | Notes |
|---|---|---|---|
| Landscape | 1280 × 720 | 16:9 | The common default; scales cleanly to 1920 × 1080 desktops |
| Portrait | 720 × 1280 | 9:16 | Same numbers, rotated |
| Pixel art | 320 × 180 or 640 × 360 | 16:9 | Divides evenly into 720p and 1080p for integer scaling |
Phones are taller than 16:9. The Pixel 7 profile's viewport above is roughly 1:2.04, and the iPhone 15's full screen is about 1:2.17. So treat 16:9 as the safe area where everything important lives, and draw backgrounds a little beyond it (to about 2.2:1) so a tall screen shows more scenery instead of black bars.
For art, think in displayed pixels at a ratio of 2. A button shown 64 CSS pixels wide needs a 128-pixel-wide texture to stay sharp on a phone. Full-screen art should match the largest buffer you expect: about 824 × 1678 for the Pixel 7 profile at ratio 2, more for tablets and desktop monitors. Much larger than your biggest target only costs download size and memory.
Scale modes: fit, crop, extend or resize
Once you have a base resolution, decide how it meets the real screen. Phaser's Scale Manager names the standard options:
| Mode | What the player sees | Good for |
|---|---|---|
| FIT | The whole base area, with bars on two sides | Puzzles and board games where every cell must be visible |
| ENVELOP | The screen fully covered, edges cropped | Backgrounds, menus, games with nothing important near the edges |
| EXPAND | The base area fitted, and the canvas extended to fill the rest | Most action games: no bars, nothing important lost |
| RESIZE | The canvas matches the screen; you lay everything out yourself | Responsive games and UIs |
| NONE | No scaling; you control sizes completely | Custom sizing like the device-pixel code above |
Without an engine, fit and extend are one line each:
// FIT: largest scale that shows the whole base area
const fit = Math.min(w / BASE_W, h / BASE_H);
// EXTEND: keep the base height and show more or less world sideways
const scale = h / BASE_H;
const visibleWorldWidth = w / scale;Pixel art needs whole-number scale factors (2×, 3×), ctx.imageSmoothingEnabled = false and the CSS image-rendering: pixelated on the canvas, or the pixels blur and wobble.
Responsive HTML5 game design
A responsive HTML5 canvas game doesn't just scale a picture: it lays out its HUD and sometimes its board for each screen shape. The habits that make that easy:
- One
layout(w, h)function, called fromresize, that positions everything from the real width and height. Never hard-code a button at x = 1200. - Anchor HUD elements to edges: score top-left, pause top-right, controls bottom corners. They stay put whatever the aspect ratio.
- Size boards from the smaller dimension, as our free Snake code does: the largest cell that fits both ways.
- Separate portrait and landscape layouts when the board is the game. A sorting puzzle might stack its tubes in two rows in portrait and one row in landscape.
- Respect safe areas. With
viewport-fit=cover, readenv(safe-area-inset-*)into CSS variables so the canvas code can use them too.
:root {
--safe-top: env(safe-area-inset-top);
--safe-bottom: env(safe-area-inset-bottom);
}const css = getComputedStyle(document.documentElement);
const safeTop = parseFloat(css.getPropertyValue("--safe-top")) || 0;- Use dynamic viewport units (
100dvh) for HTML overlays, since100vhon phones includes the area behind the browser bars. MDN's CSS length units page explains the small, large and dynamic viewport units.
How to stop HTML5 game scaling you don't want
"Stop the scaling" means different things depending on what you're seeing:
| Symptom | Cause | Fix |
|---|---|---|
| The page zooms on pinch or double tap | Browser zoom on a normal page | touch-action: none on the game element |
user-scalable=no is ignored on iPhone | WebKit ignores it since iOS 10 | Same: touch-action, not the viewport tag |
| The canvas looks blurry | The browser stretches a small buffer | Buffer = CSS size × pixel ratio, as above |
| The game should stay one fixed size on desktop | A scale mode is resizing it | Fixed CSS width and height, scale mode NONE, centre with CSS |
| Sharpness changes when the player zooms or moves to another monitor | The pixel ratio changed | Re-run resize when the ratio changes (code below) |
| HTML text grows in landscape on iPhone | Safari's text inflation | text-size-adjust: 100% (with the -webkit- prefix) |
Browser zoom and moving a window to a monitor with a different density both change devicePixelRatio without always resizing the window. A shorter version of the pattern on MDN's devicePixelRatio page creates a new media query for each ratio:
function watchPixelRatio() {
matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`)
.addEventListener("change", () => { resize(); watchPixelRatio(); }, { once: true });
}
watchPixelRatio();Fullscreen on Android and iPhone
On Android, Chrome hides its toolbar when you call requestFullscreen() from a tap or click, and in fullscreen you can also lock the orientation. In Phaser, call this.scale.startFullscreen() from a pointerup handler. If the game runs inside an iframe, the iframe needs allow="fullscreen", or the request fails.
On iPhone there is no element Fullscreen API. Safari supports it on iPad from version 16.4, with an overlay button you can't hide and a swipe-down that exits. On iPhone, the options are the browser bars, a Home Screen web app with "display": "fullscreen" or "standalone" in its manifest, or an app wrapper. Check document.fullscreenEnabled and hide the fullscreen button when it's false.
Whichever way the game goes fullscreen, the viewport changes size, so your resize handler runs and the layout adapts. If the game must be full screen and locked to one orientation on every phone, wrapping it as an app is the reliable route: see turning an HTML5 game into an Android, iOS or desktop app.
Games that already scale to every screen
Across our 50 games, 27 are landscape-only, 13 are portrait-only and 10 play both ways, and all of them run through end-to-end tests on desktop browsers and on the emulated Pixel 7 and iPhone 15 profiles above. These three switch between portrait and landscape:
Glass Shelf Triple
Select three identical objects from a layered display cabinet; clearing front objects reveals deeper layers.
Bakery Blast
Complete fast matching boards to earn resources used to design and expand themed bakeries.
Hook Hero Neon
Tap and hold to grapple neon anchors, swing, release and chain momentum through glowing obstacle courses.
Browse all of our mobile-ready HTML5 games, or see Phaser game source code if you want to study a Phaser 4 scaling setup. For keeping a high-resolution canvas fast, read our guide to HTML5 game performance.
Frequently asked questions
Why is my HTML5 canvas blurry on phones?
The drawing buffer is smaller than the screen's physical pixels, so the browser stretches it. Set canvas.width and canvas.height to the CSS size times devicePixelRatio (capped at 2), then scale the context by the same ratio so your drawing code still uses CSS pixels.
What canvas size should a mobile HTML5 game use?
Fill the visible viewport, not a fixed size: read innerWidth and innerHeight, size the buffer by the pixel ratio, and map a base resolution such as 720 × 1280 (portrait) or 1280 × 720 (landscape) onto it with a fit or extend scale.
Should I use FIT or RESIZE in Phaser?
Use FIT when every part of a fixed board must be visible and bars are acceptable. Use RESIZE, or EXPAND, when the game should fill the screen with no bars, and lay out the HUD from the real size in a resize handler.
Can an HTML5 game go fullscreen on iPhone?
Not in the browser: iPhone Safari doesn't support the Fullscreen API for page elements. Players can add the game to the Home Screen, which opens it without Safari's bars, or you can ship it as an app.
We make and sell HTML5 game source code; links to our games are marked as ours.


