| name | web-game-optimization |
| description | Optimize web-based games for performance, accessibility, and cross-device compatibility. Use when improving game speed, reducing load times, or enhancing game accessibility. |
Web Game Optimization
Optimize your games for smooth performance across all devices, from high-end desktops to low-end mobile phones. This includes canvas optimization for complex scenes, managing many entities, synthesized audio, and frame-rate independent animations.
Performance Analysis & Measurement
FPS and Frame Time Monitoring
class PerformanceMonitor {
constructor() {
this.frameCount = 0;
this.fps = 0;
this.frameTime = 0;
this.lastTime = performance.now();
}
update() {
const now = performance.now();
this.frameTime = now - this.lastTime;
this.lastTime = now;
this.frameCount++;
}
logMetrics() {
if (this.frameCount % 60 === 0) {
console.log(`FPS: ${(1000 / this.frameTime).toFixed(1)}, ` +
`Frame Time: ${this.frameTime.toFixed(2)}ms`);
}
}
}
Memory Profiling
if (performance.memory) {
const used = Math.round(performance.memory.usedJSHeapSize / 1048576);
const limit = Math.round(performance.memory.jsHeapSizeLimit / 1048576);
console.log(`Memory: ${used}MB / ${limit}MB`);
}
Chrome DevTools Integration
performance.mark('game-update-start');
performance.mark('game-update-end');
performance.measure('game-update', 'game-update-start', 'game-update-end');
const measures = performance.getEntriesByType('measure');
measures.forEach(m => console.log(`${m.name}: ${m.duration.toFixed(2)}ms`));
Rendering Optimization
Canvas Optimization
Canvas Optimization
Using performance.now() for Frame-Rate Independent Animation
For consistent animation across different frame rates, use wall-clock time instead of frame counting:
const timeSeconds = performance.now() * 0.001;
const breathe = Math.sin(timeSeconds * 2) * 2;
ctx.translate(x, y + breathe);
const wavePhase = timeSeconds * 0.5 + xPosition * 0.02;
ctx.lineTo(x, y + Math.sin(wavePhase) * amplitude);
Rendering Complex Scenes with Multiple Layers
For games with many arenas, particles, and decorative elements:
class RenderBatcher {
constructor(ctx) {
this.ctx = ctx;
this.batches = {};
}
add(layer, x, y, width, height, fillColor) {
const key = `${layer}-${fillColor}`;
if (!this.batches[key]) this.batches[key] = [];
this.batches[key].push({x, y, width, height});
}
flush() {
for (const [key, rects] of Object.entries(this.batches)) {
const [, color] = key.split('-');
this.ctx.fillStyle = color;
for (const rect of rects) {
this.ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
}
}
this.batches = {};
}
}
Object Pooling for Weapons/Projectiles
Reuse objects instead of creating/destroying to reduce garbage collection:
class ProjectilePool {
constructor(maxSize = 100) {
this.pool = [];
this.active = [];
for (let i = 0; i < maxSize; i++) {
this.pool.push(new Projectile());
}
}
create(x, y, vx, vy) {
let proj = this.pool.pop();
if (!proj) proj = new Projectile();
proj.reset(x, y, vx, vy);
this.active.push(proj);
return proj;
}
update(dt) {
for (let i = this.active.length - 1; i >= 0; i--) {
this.active[i].update(dt);
if (this.active[i].isDead) {
this.pool.push(this.active.splice(i, 1)[0]);
}
}
}
}
Web Audio Synthesis vs Pre-recorded
Web Audio API synthesis is CPU-light for short sound effects but uses CPU. Pre-recorded audio is better for continuous loops:
const synth = (freq = 60, duration = 0.1, vol = 0.1) => {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.frequency.value = freq;
gain.gain.setValueAtTime(vol, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + duration);
};
synth(100, 0.15);
synth(80, 0.2);
DOM Rendering Optimization
element.style.transform = 'translate3d(x, y, 0)';
element.style.opacity = 0.5;
element.style.left = x + 'px';
element.style.top = y + 'px';
const fragment = document.createDocumentFragment();
for (let i = 0; i < count; i++) {
const el = document.createElement('div');
fragment.appendChild(el);
}
container.appendChild(fragment);
element.classList.toggle('active');
WebGL Optimization
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2');
JavaScript Optimization
Memory Management
class GameEntity {
constructor() {
this.listeners = [];
}
addEventListener(event, handler) {
this.listeners.push({ event, handler });
element.addEventListener(event, handler);
}
destroy() {
this.listeners.forEach(({ event, handler }) => {
element.removeEventListener(event, handler);
});
this.listeners = [];
}
}
class BulletPool {
constructor(size = 1000) {
this.available = [];
for (let i = 0; i < size; i++) {
this.available.push(new Bullet());
}
this.active = [];
}
get() {
return this.available.pop() || new Bullet();
}
return(bullet) {
bullet.reset();
this.available.push(bullet);
}
update(dt) {
const toReturn = [];
this.active.forEach(bullet => {
bullet.update(dt);
if (bullet.isDead) {
toReturn.push(bullet);
}
});
toReturn.forEach(b => {
this.active.splice(this.active.indexOf(b), 1);
this.return(b);
});
}
}
const velocity = { x: 0, y: 0 };
velocity.x = newVelX;
velocity.y = newVelY;
Algorithm Optimization
class SpatialIndex {
constructor(cellSize = 100) {
this.cellSize = cellSize;
this.grid = new Map();
}
insert(entity) {
const key = this.getCellKey(entity);
if (!this.grid.has(key)) {
this.grid.set(key, []);
}
this.grid.get(key).push(entity);
}
getCellKey(entity) {
const x = Math.floor(entity.x / this.cellSize);
const y = Math.floor(entity.y / this.cellSize);
return `${x},${y}`;
}
getNearby(entity) {
const nearby = [];
const key = this.getCellKey(entity);
const [cx, cy] = key.split(',').map(Number);
for (let x = cx - 1; x <= cx + 1; x++) {
for (let y = cy - 1; y <= cy + 1; y++) {
const cellKey = `${x},${y}`;
nearby.push(...(this.grid.get(cellKey) || []));
}
}
return nearby;
}
}
const sinLookup = new Float32Array(360);
for (let i = 0; i < 360; i++) {
sinLookup[i] = Math.sin(i * Math.PI / 180);
}
for (let i = 0; i < entities.length; i++) {
entities[i].draw(ctx, Math.cos(rotation), Math.sin(rotation));
}
const cos = Math.cos(rotation);
const sin = Math.sin(rotation);
for (let i = 0; i < entities.length; i++) {
entities[i].draw(ctx, cos, sin);
}
Asset Loading & Optimization
Lazy Loading
class AssetLoader {
constructor() {
this.assets = new Map();
this.loading = new Map();
}
async load(key, src) {
if (this.loading.has(key)) {
return this.loading.get(key);
}
const promise = this.loadAsset(key, src);
this.loading.set(key, promise);
try {
await promise;
this.loading.delete(key);
} catch (e) {
this.loading.delete(key);
throw e;
}
}
async loadAsset(key, src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
this.assets.set(key, img);
resolve(img);
};
img.onerror = reject;
img.src = src;
});
}
get(key) {
return this.assets.get(key);
}
}
Image Compression & Formats
const imageUrl =
'image.webp'
: 'image.png';
Audio Optimization
class AudioManager {
preloadAudio(key, src) {
const audio = new Audio();
audio.src = src;
this.assets.set(key, audio);
}
}
Mobile Optimization
Device Detection
const deviceInfo = {
isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent),
isTablet: /iPad|Android(?!.*Mobile)/i.test(navigator.userAgent),
isLandscape: window.innerWidth > window.innerHeight,
pixelRatio: window.devicePixelRatio
};
if (deviceInfo.isMobile && deviceInfo.pixelRatio <= 2) {
gameConfig.maxParticles = 100;
gameConfig.enableShadows = false;
}
Touch Performance
element.addEventListener('touchmove', handleMove, { passive: true });
element.addEventListener('touchstart', handleStart, { passive: true });
let lastTouchTime = 0;
const TOUCH_THROTTLE = 1000 / 60;
document.addEventListener('touchmove', (e) => {
const now = Date.now();
if (now - lastTouchTime < TOUCH_THROTTLE) return;
lastTouchTime = now;
handleTouchMove(e);
}, { passive: true });
Battery & Thermal Management
const lowPowerMode = navigator.deviceMemory <= 4;
const slowConnection = navigator.connection?.effectiveType === '4g';
if (lowPowerMode) {
gameConfig.targetFPS = 30;
gameConfig.maxEntities = 200;
}
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
game.pause();
} else {
game.resume();
}
});
Accessibility in Games
Screen Reader Support
const gameContainer = document.querySelector('#game');
gameContainer.setAttribute('role', 'application');
gameContainer.setAttribute('aria-label', 'Game Name - Use keyboard to play');
const statusDiv = document.createElement('div');
statusDiv.setAttribute('aria-live', 'polite');
statusDiv.setAttribute('aria-atomic', 'true');
statusDiv.className = 'sr-only';
function updateStatus(message) {
statusDiv.textContent = message;
}
Keyboard Navigation
class AccessibleGame {
constructor() {
this.keyBindings = {
'ArrowUp': () => this.moveUp(),
'ArrowDown': () => this.moveDown(),
'w': () => this.moveUp(),
's': () => this.moveDown(),
' ': () => this.action(),
'Enter': () => this.action()
};
window.addEventListener('keydown', (e) => {
const handler = this.keyBindings[e.key];
if (handler) {
e.preventDefault();
handler();
}
});
}
}
Color Contrast & Colorblind Support
body {
color: #222;
background: #f8f9fa;
}
[data-theme="deuteranopia"] .game-board {
--color-primary: #0173b2;
--color-secondary: #de8f05;
}
Motor Accessibility
const controlSchemes = {
default: { up: 'ArrowUp', down: 'ArrowDown', action: ' ' },
wasd: { up: 'w', down: 's', action: 'e' },
mouse: { move: 'mousemove', action: 'click' },
gamepad: { }
};
const timingConfig = {
keyPressDuration: 500,
comboWindow: 1000
};
Network Optimization
Bandwidth Reduction
const compressed = msgpack.encode(gameState);
class StateDelta {
static compute(prev, current) {
const delta = {};
for (const key in current) {
if (JSON.stringify(current[key]) !==
JSON.stringify(prev[key])) {
delta[key] = current[key];
}
}
return delta;
}
}
Debugging Performance Issues
Common Bottlenecks
- Main Thread Blocking - Move heavy work to Web Workers
- Memory Leaks - Use Chrome DevTools Memory tab
- Excessive Repaints - Check DevTools Rendering tab
- Heavy Computations - Profile with Performance tab
- Network Delays - Use Network throttling in DevTools
Profiling Tools
performance.mark('operation-start');
performance.mark('operation-end');
performance.measure('operation', 'operation-start', 'operation-end');
const measures = performance.getEntriesByType('measure');
console.table(measures);