Cracking the Firefox Performance Puzzle for "Moksha"
Hey everyone! As I’ve been testing Moksha—our vanilla JS and HTML5 Canvas game—across different browsers, I ran into a massive roadblock: Firefox was lagging heavily, while Chromium-based browsers like Chrome ran smoothly at a solid 60+ FPS.
Because Moksha uses a raw 2D canvas context without a heavyweight game engine, it relies completely on the browser's native rendering efficiency. Here is a breakdown of why this lag happens in Firefox and exactly how to fix it via browser, OS, and code-level configurations.
The Problem: Why Chrome Succeeded and Firefox Lagged
Chrome aggressively defaults to GPU acceleration for 2D graphics out of the box. Firefox, however, prioritizes safety. If it flags an older, non-standard, or dual-graphics laptop driver, it silently downgrades canvas rendering to a software-fallback engine running on your CPU instead of your graphics card. This bottlenecks our vanilla game loop.
Phase 1: Overriding Firefox's Internal Restrictions
To force Firefox to treat Moksha with the same high-performance priority as Chrome, we need to bypass its strict driver blocklists.
1. Activate Hardware Acceleration
- Open Firefox Settings -> General -> scroll down to the Performance section.
- Uncheck “Use recommended performance settings”.
- Check the box for “Use hardware acceleration when available”.
2. Bypass Driver Guardrails (about:config)
Firefox will still restrict advanced features if it doesn't trust your GPU driver. You can manually force compliance:
- Type
about:configin the address bar and accept the risk warning. - Search for
webgl.force-enabledand toggle its value totrue. - Search for
gfx.canvas.azure.accelerated(the 2D canvas pipeline) and ensure it is set totrue.
3. Maximize the WebRender Engine
Firefox features WebRender, an advanced GPU-focused rendering engine.
- Inside
about:config, search forgfx.webrender.all. - Switch its value to
trueto force hardware-accelerated composition. - Completely restart Firefox to apply the changes.
Phase 2: Diagnostics & Verification
To confirm Firefox is genuinely using the GPU hardware layer to render Moksha:
- Navigate to
about:supportin the Firefox address bar. - Scroll down to the Graphics section.
- Verify that Compositing states WebRender (not Software WebRender).
- Verify that WebGL 1/2 Driver Renderer explicitly lists your graphics card name rather than a software fallback like Mesa or WARP.
Phase 3: Code Optimizations for Vanilla JS Canvas
While browser settings fix the local environment, we also adjusted Moksha's vanilla codebase to natively demand GPU attention.
1. Force GPU Layering via CSS
By adding 3D transform hints, we force the browser to isolate the game onto its own composited hardware layer.
canvas {
transform: translate3d(0, 0, 0);
will-change: transform;
-webkit-tap-highlight-color: transparent;
touch-action: none;
}
2. Desynchronized Rendering Context
We initialized our 2D context with optimization flags. This hints the browser pipeline to bypass standard front/back UI composition buffers, minimizing frame latency.
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d', {
alpha: false, // Disables transparency calculation for a performance boost
desynchronized: true // Hints the browser to bypass standard buffers
});
Going Mobile & Taming the Android Chrome Pipeline
Hey everyone! Following up on our Firefox breakthrough, we shifted focus straight to mobile optimization for Moksha.
Testing a vanilla JS, raw HTML5 Canvas game on mobile devices is a completely different beast. Mobile browsers aggressively throttle hardware acceleration to conserve battery life, throwing our beautiful desktop frame rates straight out the window.
In this devlog, we are diving deep into how to force Android's Chrome browser to prioritize our game's performance, alongside the exact vanilla JS boilerplate we implemented to handle mobile responsive scaling and tactile feedback.
Phase 1: Unlocking Mobile Chrome's Hidden Power
Mobile Chrome heavily safeguards your phone’s CPU/GPU by default. To unlock full desktop-like rendering on a test device, we have to manipulate Chrome's experimental flag landscape.
If your mobile performance is stuttering during local testing, open Chrome on your Android device, navigate to chrome://flags, and modify these three settings:
- Override software rendering list (#ignore-gpu-blocklist) $\rightarrow$ Enabled Forces Chrome to use the GPU even if your phone's specific mobile graphics driver is technically marked as unsupported.
- Accelerated 2D canvas (#disable-2d-canvas-image-chromium) $\rightarrow$ Enabled Bypasses CPU rendering routines for our 2D canvas pipelines, providing an immediate frame rate boost.
- GPU rasterization (#enable-gpu-rasterization) $\rightarrow$ Enabled Forces the graphics processor to draw web elements and vector assets instead of leaning on mobile threads. [1]
Note: Make sure to hit Relaunch at the bottom of the flags page and clear Chrome from your phone's recent apps menu to ensure the configurations cycle properly.
Phase 2: Perfecting the Mobile Canvas Layout
By default, mobile browsers treat web pages like desktop sites, causing canvas layouts to shrink or zoom out drastically. We fixed this globally by injecting an explicit viewport meta tag into our index.html header:
The Breakdown:
- user-scalable=no: Stops the browser from accidentally zooming the layout out of boundaries when players rapidly tap the on-screen action controls.
- viewport-fit=cover: Forces our canvas to bleed beautifully edge-to-edge, seamlessly utilizing the dead space behind modern phone camera notches.
We matched this with a clean CSS layer to eradicate mobile "rubber-banding" scroll behavior:
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden; /* Eradicates mobile layout bounce effects */
background-color: #000;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
}
Phase 3: Adding Physical Polish with Native Haptics
Mobile controls feel floaty and detached without tactile response. To bridge the gap between a simple web page and a native application, we engineered a lightweight, defensive Haptic Manager Utility in vanilla JavaScript using the Web Vibration API.
Because iOS Safari fundamentally blocks vibrations, and modern mobile browsers require explicit user interaction before unlocking hardware motors, we wrapped our system defensively:
const Haptics = {
// Safely check if the host mobile platform supports hardware vibrations
isSupported: typeof navigator !== 'undefined' && !!navigator.vibrate,
// Light tap: Perfect for UI buttons, menus, or rapid shooting mechanics
light() {
if (this.isSupported) navigator.vibrate(12); // Short 12ms pulse
},
// Medium pulse: Designed for jumps, item collection, or minor damage states
medium() {
if (this.isSupported) navigator.vibrate(35); // Snappy 35ms response
},
// Heavy impact: Reserved for explosions, player deaths, or camera shakes
heavy() {
if (this.isSupported) navigator.vibrate(100); // Sustained 100ms rumble
}
};
Eliminating Input Lag
When mapping this to mobile UI, we completely abandoned native click listeners. Standard clicks introduce an internal 300ms hardware delay as mobile browsers wait to see if you are double-tapping to zoom. Instead, we wired our controls straight to instantaneous touchstart routines:
const jumpButton = document.getElementById('jumpBtn');
jumpButton.addEventListener('touchstart', (event) => {
event.preventDefault(); // Terminates the simulated "click" loop delay
Haptics.light(); // Immediate physical response
player.jump(); // Execute game logic
}, { passive: false });
Top comments (0)