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.

A person using a touchscreen interface
Touch precision is measured in fingers, not pixels — the whole layout problem in one sentence. Image via Wikipedia.

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: manipulation on 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 */
    
  • pointercancel fires 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

TestPass criteria
iPhone SE (375px width)Everything reachable, no horizontal scroll
iPad / 768pxLayout adapts without stretching UI to absurdity
Rotate mid-gameNo corrupted layout, no lost input state
Address bar collapseCanvas never jumps or clips
Notched deviceNo controls under the notch or home indicator
Weak networkFirst frame playable before secondary assets
Low-power modeFrame rate drops gracefully, gameplay intact
Incoming notification mid-touchpointercancel 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.

Sources