Most H5 game traffic arrives on phones, and most H5 games are designed for a desktop monitor first, then “made to fit” a phone afterwards. That sequence guarantees the two worst outcomes in mobile design: buttons too small for thumbs, and a canvas that jumps when the browser toolbar collapses. Both are avoidable, both are common, and both are decided in the first hour of a project rather than during QA.
This is the mobile-first checklist we wish every H5 project started with.

1. The 100vh Trap (And dvh)
The classic bug: the game canvas is height: 100vh, the mobile browser’s address bar collapses on scroll, and the bottom of the game ends up underneath the toolbar — or a gap appears where the page is taller than the visible area. On mobile, vh units refer to the largest possible viewport, not the visible one.
The fix is the dynamic viewport unit family, supported in Safari since 15.4, Firefox since 101 and Chrome since 108:
.game-container {
height: 100vh; /* fallback for very old browsers */
height: 100dvh; /* tracks the visible viewport as bars move */
}
@supports (height: 100dvh) {
.game-container { height: 100dvh; }
}
svh— smallest (toolbar fully visible)lvh— largest (toolbar collapsed)dvh— current, changing value: what you almost always want for a game screen
Combine with position: fixed and overscroll-behavior: none on the game shell so rubber-band scrolling cannot pull your layout out of alignment during play.
2. Safe Areas and Notches
If you let the page stretch edge to edge — which you should, for a game — you must also keep interactive UI out of the notch, the home indicator and the rounded corners:
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
.hud {
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
padding-bottom: env(safe-area-inset-bottom);
}
viewport-fit=cover opts into the full screen; env(safe-area-inset-*) hands back the insets the OS is using for system UI. Without the meta tag, the insets read as zero and your carefully placed pause button sits under the clock.
3. Touch Targets: 44 and 48
Mouse precision on a desktop is pixel-level. Finger precision is centimeter-level, and fingers are attached to hands that are also holding the device.
- Apple’s Human Interface Guidelines: minimum 44×44pt touch targets.
- Material Design: minimum 48×48dp.
/* Wrong: a desktop-sized button on a phone */
.button { width: 30px; height: 30px; }
/* Right: a target a thumb can hit while the bus moves */
.button {
min-width: 48px;
min-height: 48px;
padding: 14px 24px;
}
Keep primary controls in the lower third of the screen where thumbs naturally rest, avoid anything requiring two simultaneous fingers unless the game is about multitouch, and never place two high-frequency buttons within a thumb’s sliding distance of each other.
4. Input Handling: Pointer Events, Not Four Listeners
Pointer Events unify mouse, touch and pen into one model — one handler instead of click, touchstart, touchend and a graveyard of device sniffing:
gameArea.addEventListener('pointerdown', onDown);
gameArea.addEventListener('pointermove', onMove);
window.addEventListener('pointerup', onUp);
gameArea.addEventListener('pointercancel', onUp); // finger leaves the surface
Two details that prevent real bugs:
touch-action: manipulationon interactive elements removes the double-tap-zoom delay, which otherwise adds perceptible latency to every tap:.game-area { touch-action: none; } /* game surface: we handle gestures */ .ui-panel { touch-action: manipulation; } /* buttons: no double-tap zoom */pointercancelfires when the browser takes over the gesture (incoming call, notification, scroll). If you do not handle it, your character keeps moving after the player’s finger is gone.
For scroll-free game screens, register touchmove listeners as passive: false only where you genuinely need preventDefault() — everywhere else, passive listeners keep scrolling smooth.
5. Canvas Sharpness and Sizing
A canvas sized in CSS pixels but drawn at device resolution looks blurry on a 3× phone. Size the drawing buffer to the device pixel ratio, cap it to keep fill rate sane, and let CSS handle layout:
function resize() {
const dpr = Math.min(window.devicePixelRatio || 1, 2); // cap at 2
const w = gameArea.clientWidth;
const h = gameArea.clientHeight;
canvas.width = Math.round(w * dpr);
canvas.height = Math.round(h * dpr);
canvas.style.width = w + 'px';
canvas.style.height = h + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
window.addEventListener('resize', resize);
window.addEventListener('orientationchange', () => setTimeout(resize, 150));
The orientationchange delay matters: browsers report stale dimensions immediately after rotation, and a resize scheduled a beat later avoids corrupting the layout.
6. Graceful Degradation
Not every phone in the audience can sustain 60fps with 2,000 particles:
const lowPower =
(navigator.hardwareConcurrency || 8) <= 4 ||
(navigator.deviceMemory || 4) <= 4 ||
window.innerWidth < 768;
const particleBudget = lowPower ? 500 : 2000;
const targetFps = lowPower ? 30 : 60;
Pair it with network discipline: preload only what the first frame needs, then stream audio, secondary spritesheets and heavy assets a few seconds after gameplay starts. On flaky mobile connections, deferred loading is not an optimization — it is the difference between playing and staring at a spinner.
7. Testing Checklist
| Test | Pass criteria |
|---|---|
| iPhone SE (375px width) | Everything reachable, no horizontal scroll |
| iPad / 768px | Layout adapts without stretching UI to absurdity |
| Rotate mid-game | No corrupted layout, no lost input state |
| Address bar collapse | Canvas never jumps or clips |
| Notched device | No controls under the notch or home indicator |
| Weak network | First frame playable before secondary assets |
| Low-power mode | Frame rate drops gracefully, gameplay intact |
| Incoming notification mid-touch | pointercancel resets input cleanly |
Conclusion
Responsiveness is not “scale the page down.” It is designing the correct experience for each device, each orientation and each moment the browser decides to move its own chrome around. The web’s gift to H5 games is that one build reaches every screen — but only if the build was designed for the smallest, weirdest, most constrained screen in the room first, and treated the desktop as the easy case afterwards.