DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

Canvas 2D and WebGL in Firefox: Drawing, Performance, and Debugging

Firefox has excellent support for Canvas 2D and WebGL. This guide covers the practical API, performance tips, and how to debug graphics with Firefox DevTools.

Canvas 2D Basics

<canvas id="myCanvas" width="800" height="600"></canvas>
Enter fullscreen mode Exit fullscreen mode
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// Shapes
ctx.fillStyle = '#3B82F6';
ctx.fillRect(10, 10, 100, 80);

ctx.strokeStyle = '#1E40AF';
ctx.lineWidth = 2;
ctx.strokeRect(10, 10, 100, 80);

// Paths
ctx.beginPath();
ctx.moveTo(200, 50);
ctx.lineTo(300, 150);
ctx.lineTo(100, 150);
ctx.closePath();
ctx.fillStyle = '#10B981';
ctx.fill();
ctx.stroke();

// Circles
ctx.beginPath();
ctx.arc(400, 100, 50, 0, Math.PI * 2);
ctx.fillStyle = '#F59E0B';
ctx.fill();
Enter fullscreen mode Exit fullscreen mode

Text Rendering

ctx.font = '24px "Inter", sans-serif';
ctx.fillStyle = '#1F2937';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Hello Canvas', canvas.width / 2, canvas.height / 2);

// Measure text
const metrics = ctx.measureText('Hello');
console.log(metrics.width); // pixel width
console.log(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent); // height
Enter fullscreen mode Exit fullscreen mode

Images and Sprites

const img = new Image();
img.src = '/sprites/player.png';
img.onload = () => {
  // Draw full image
  ctx.drawImage(img, 0, 0);

  // Draw scaled
  ctx.drawImage(img, 0, 0, 200, 200);

  // Draw sprite (source x, y, w, h → dest x, y, w, h)
  ctx.drawImage(img, 64, 0, 32, 32, 100, 100, 64, 64);
};
Enter fullscreen mode Exit fullscreen mode

Canvas Performance Tips

// 1. Clear only what changed, not the whole canvas
function clearRegion(x, y, w, h) {
  ctx.clearRect(x, y, w, h);
}

// 2. Use requestAnimationFrame, not setInterval
function gameLoop(timestamp) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  update(timestamp);
  draw(ctx);
  requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

// 3. Use an offscreen canvas for complex static content
const offscreen = new OffscreenCanvas(800, 600);
const offCtx = offscreen.getContext('2d');
// Draw complex background once
drawBackground(offCtx);

// In game loop, blit the offscreen canvas
function draw(ctx) {
  ctx.drawImage(offscreen, 0, 0); // Fast copy
  // Draw dynamic content on top
  drawPlayer(ctx);
}

// 4. Batch operations — avoid switching fill/stroke colors repeatedly
// Bad:
items.forEach(item => {
  ctx.fillStyle = item.color;
  ctx.fillRect(item.x, item.y, item.w, item.h);
});

// Better: group by color
const grouped = Map.groupBy(items, i => i.color);
grouped.forEach((items, color) => {
  ctx.fillStyle = color;
  ctx.beginPath();
  items.forEach(item => ctx.rect(item.x, item.y, item.w, item.h));
  ctx.fill();
});
Enter fullscreen mode Exit fullscreen mode

WebGL Basics

For 3D or high-performance 2D, use WebGL:

const gl = canvas.getContext('webgl2'); // Firefox supports WebGL 2

// Vertex shader
const vertexShaderSrc = `#version 300 es
in vec2 aPosition;
void main() {
  gl_Position = vec4(aPosition, 0.0, 1.0);
}`;

// Fragment shader
const fragmentShaderSrc = `#version 300 es
precision highp float;
out vec4 fragColor;
void main() {
  fragColor = vec4(0.231, 0.510, 0.965, 1.0); // #3B82F6
}`;

function createShader(type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    throw new Error(gl.getShaderInfoLog(shader));
  }
  return shader;
}

const vertShader = createShader(gl.VERTEX_SHADER, vertexShaderSrc);
const fragShader = createShader(gl.FRAGMENT_SHADER, fragmentShaderSrc);

const program = gl.createProgram();
gl.attachShader(program, vertShader);
gl.attachShader(program, fragShader);
gl.linkProgram(program);

// Draw a triangle
const vertices = new Float32Array([
  0, 0.5,   // top
  -0.5, -0.5, // bottom-left
  0.5, -0.5,  // bottom-right
]);

const vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

gl.useProgram(program);
const posLoc = gl.getAttribLocation(program, 'aPosition');
gl.enableVertexAttribArray(posLoc);
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);

gl.clearColor(0.1, 0.1, 0.1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 3);
Enter fullscreen mode Exit fullscreen mode

Debugging Canvas with Firefox DevTools

Firefox has dedicated canvas debugging tools:

  1. Memory Snapshot: DevTools → Memory → Take snapshot to see canvas contexts and GPU memory usage
  2. Performance: Record a timeline to see frame rendering time, GPU raster time
  3. Canvas Inspector (via WebExtension): Firefox has getContext hooks — you can log all draw calls for debugging
// Manual draw call logging for debugging
const origFillRect = CanvasRenderingContext2D.prototype.fillRect;
CanvasRenderingContext2D.prototype.fillRect = function(x, y, w, h) {
  console.trace('fillRect', { x, y, w, h });
  return origFillRect.apply(this, arguments);
};
Enter fullscreen mode Exit fullscreen mode

Saving Canvas as Image

// PNG download
canvas.toBlob(blob => {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'canvas-export.png';
  a.click();
  URL.revokeObjectURL(url);
}, 'image/png');

// JPEG with quality
canvas.toBlob(blob => { /* ... */ }, 'image/jpeg', 0.9);

// Synchronous (avoid for large canvases — can block UI)
const dataUrl = canvas.toDataURL('image/png');
Enter fullscreen mode Exit fullscreen mode

Three.js with Firefox

For complex 3D, Three.js builds on WebGL and works great in Firefox:

import * as THREE from 'three';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshPhongMaterial({ color: 0x3B82F6 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 5, 5);
scene.add(light);

camera.position.z = 5;

function animate() {
  requestAnimationFrame(animate);
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  renderer.render(scene, camera);
}
animate();
Enter fullscreen mode Exit fullscreen mode

Firefox's WebGL implementation follows the spec closely — code that works in Firefox generally works everywhere.

Top comments (0)