Browser-specific constraints reference for web games. Covers tab visibility and RAF throttling, iOS audio context unlock, Fullscreen API, Pointer Lock, Screen Wake Lock, save data (localStorage vs IndexedDB), Service Worker asset caching, mobile memory pressure, and Web Workers for offloading heavy computation. Complements three-js-best-practices and phaser-best-practices with browser-environment concerns that apply regardless of engine.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Browser-specific constraints reference for web games. Covers tab visibility and RAF throttling, iOS audio context unlock, Fullscreen API, Pointer Lock, Screen Wake Lock, save data (localStorage vs IndexedDB), Service Worker asset caching, mobile memory pressure, and Web Workers for offloading heavy computation. Complements three-js-best-practices and phaser-best-practices with browser-environment concerns that apply regardless of engine.
license
MIT
compatibility
Portable reference skill for agents that support markdown skills or prompt files. Engine-agnostic — applies to Phaser, Three.js, Babylon.js, plain canvas, or any browser game runtime.
Reference guide for browser-specific concerns that apply to all web games regardless of engine. Rules are grouped by constraint type. Critical rules are marked CRITICAL.
1. Tab Visibility and RAF Throttling
CRITICAL — requestAnimationFrame is throttled or suspended when a tab is hidden. Timers also drift. If your game loop relies on wall-clock time, unhandled tab switching will cause:
physics simulation tunnelling (objects teleport through walls on return)
audio desync
incorrect delta accumulation (a 30-second tab switch produces a 30-second delta on return)
Pause and resume on visibility change
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
pauseGame(); // stop game loop, mute audio, save state
} else {
resetDelta(); // discard accumulated delta timeresumeGame();
}
});
Clamping delta time
Always clamp the maximum delta to a safe value before passing it to your update loop:
On mobile browsers, background tabs may be fully suspended (not just throttled). Treat any delta > 250 ms as an indication the tab was backgrounded — reset physics integrators and particle timers rather than simulating the gap.
2. Audio Context — iOS and Mobile Unlock
CRITICAL — Safari (iOS and macOS) and many mobile browsers suspend the AudioContext until a user gesture. Any attempt to play audio before the first gesture produces silence, or a NotAllowedError.
Pointer Lock captures the mouse cursor inside the canvas, enabling unlimited relative movement. Required for first-person games and mouse-look controls.
const canvas = document.getElementById('game-canvas');
// Request — must be called from a user gesture
canvas.addEventListener('click', () => {
canvas.requestPointerLock();
});
// Listen for lock/unlockdocument.addEventListener('pointerlockchange', () => {
if (document.pointerLockElement === canvas) {
document.addEventListener('mousemove', onMouseMove);
} else {
document.removeEventListener('mousemove', onMouseMove);
}
});
functiononMouseMove(e) {
// movementX / movementY give unbounded relative deltasrotateCamera(e.movementX * sensitivity, e.movementY * sensitivity);
}
Browsers may reject Pointer Lock on insecure origins (HTTP). Serve over HTTPS in production.
5. Screen Wake Lock
Prevents the device from sleeping during gameplay. Essential for mobile games where player inactivity on menus or cutscenes would otherwise lock the screen.
let wakeLock = null;
asyncfunctionrequestWakeLock() {
if (!('wakeLock'in navigator)) return; // not supported — fail silentlytry {
wakeLock = await navigator.wakeLock.request('screen');
wakeLock.addEventListener('release', () => {
// Re-acquire on tab re-focus if still playingif (!document.hidden) requestWakeLock();
});
} catch (err) {
console.warn('Wake lock not acquired:', err);
}
}
asyncfunctionreleaseWakeLock() {
if (wakeLock) {
await wakeLock.release();
wakeLock = null;
}
}
// Acquire when game starts, release on pause/menu/game-overdocument.addEventListener('visibilitychange', () => {
if (document.hidden) {
releaseWakeLock();
} else {
requestWakeLock();
}
});
Wake lock is released automatically when the page is backgrounded — always re-request on visibility restore.
if ('serviceWorker'in navigator) {
navigator.serviceWorker.register('/sw.js').catch(console.warn);
}
Update the cache name (game-v1 → game-v2) on every deployment that changes cached assets. The old cache is deleted in the activate handler.
8. Mobile Memory Pressure
iOS fires a private memory warning; Chrome on Android exposes onmemorypressure. Neither is reliable, but you can listen for performance degradation signals: