Dev.to WebDev 🛠 Dev 👁 0 📖 4 min read

Canvas and WebGL in Firefox: 2D Drawing, Image Processing, Three.js, and DevTools Debugging

Canvas and WebGL are the browser's APIs for 2D and 3D graphics rendering. Firefox has strong support for both, plus excellent DevTools for inspecting canvas operations. This guide covers Canvas 2D for practical tasks and

Canvas and WebGL are the browser's APIs for 2D and 3D graphics rendering. Firefox has strong support for both, plus excellent DevTools for inspecting canvas operations. This guide covers Canvas 2D for practical tasks and WebGL for developers getting into GPU-accelerated graphics.

Canvas 2D

The <canvas> element provides a bitmap drawing surface. You draw on it with the 2D rendering context API.

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// Size the canvas (not via CSS — CSS just scales pixels)
canvas.width = 800;
canvas.height = 600;

// Draw a rectangle
ctx.fillStyle = '#0066cc';
ctx.fillRect(50, 50, 200, 100);

// Draw a circle
ctx.beginPath();
ctx.arc(400, 300, 50, 0, Math.PI * 2);
ctx.fillStyle = 'red';
ctx.fill();

// Draw text
ctx.font = '24px sans-serif';
ctx.fillStyle = '#000';
ctx.fillText('Hello Canvas', 300, 200);

Canvas for Image Processing

// Load an image and draw it
const img = new Image();
img.onload = () => {
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

  // Get pixel data
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  const data = imageData.data; // Uint8ClampedArray: [R, G, B, A, R, G, B, A, ...]

  // Grayscale filter
  for (let i = 0; i < data.length; i += 4) {
    const avg = (data[i] + data[i+1] + data[i+2]) / 3;
    data[i] = avg;     // R
    data[i+1] = avg;   // G
    data[i+2] = avg;   // B
    // data[i+3] is alpha — leave it
  }

  ctx.putImageData(imageData, 0, 0);
};
img.src = '/photo.jpg';

Firefox privacy consideration: cross-origin images make the canvas "tainted" — getImageData() throws a SecurityError for tainted canvases. This prevents cross-origin pixel data theft. Same-origin or CORS-enabled images don't have this restriction.

Animation Loop

function animate(timestamp) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Update and draw
  drawBackground(ctx);
  drawSprites(ctx, timestamp);

  requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

requestAnimationFrame syncs to the display refresh rate (typically 60Hz or 120Hz). Better than setInterval for animation — pauses when the tab is backgrounded (saves CPU/battery) and handles high-refresh displays.

WebGL: GPU-Accelerated Graphics

WebGL exposes OpenGL ES 2.0/3.0 to JavaScript. It's more complex than Canvas 2D but handles millions of vertices and effects that would be impossibly slow in Canvas 2D.

const gl = canvas.getContext('webgl2'); // WebGL 2 = OpenGL ES 3.0

// Clear to a color
gl.clearColor(0.1, 0.1, 0.2, 1.0); // RGBA (0-1 range)
gl.clear(gl.COLOR_BUFFER_BIT);

Shaders are GPU programs written in GLSL. WebGL requires at minimum a vertex shader and a fragment shader:

// Vertex shader — runs for each vertex
const vertexShaderSource = `
  attribute vec2 a_position;
  void main() {
    gl_Position = vec4(a_position, 0.0, 1.0);
  }
`;

// Fragment shader — runs for each pixel
const fragmentShaderSource = `
  precision mediump float;
  uniform vec4 u_color;
  void main() {
    gl_FragColor = u_color;
  }
`;

// Compile shaders
function compileShader(gl, source, type) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    console.error('Shader error:', gl.getShaderInfoLog(shader));
    gl.deleteShader(shader);
    return null;
  }
  return shader;
}

const vertShader = compileShader(gl, vertexShaderSource, gl.VERTEX_SHADER);
const fragShader = compileShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER);

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

Three.js for Practical 3D

Three.js abstracts WebGL complexity into a usable API:

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({ canvas: document.getElementById('three-canvas') });

renderer.setSize(window.innerWidth, window.innerHeight);

// Create a box
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x0066cc });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

camera.position.z = 5;

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

Three.js works in Firefox — WebGL hardware acceleration is enabled by default.

Firefox DevTools for Canvas Debugging

Firefox's DevTools Canvas panel (enable in DevTools Settings → Canvas) records draw calls:

  1. Open DevTools → Canvas
  2. Click Record to start capturing
  3. Perform the canvas operation
  4. Stop recording

The recorded calls show:

  • Every drawing operation in order
  • The canvas state at each step
  • Which JavaScript called the draw operation

This is invaluable for finding out why something is drawn in the wrong order, wrong color, or wrong position.

WebGL in Firefox: Hardware Acceleration Check

about:support → Graphics section → WebGL Renderer

Should show your GPU name (e.g., "ANGLE (Intel, Intel(R) HD Graphics 620 Direct3D11)"). If it shows "Software rendering" or "Mesa Software Rasterizer", hardware acceleration is off.

Enable hardware acceleration: Settings → General → Performance → Use hardware acceleration when available.

Canvas Performance in Firefox

Avoid reading pixels in hot paths: getImageData() is slow — it forces a GPU-to-CPU synchronization. Read pixels only when necessary, not every frame.

Use willReadFrequently hint for canvases that frequently read pixels:

const ctx = canvas.getContext('2d', { willReadFrequently: true });

This tells Firefox to keep the bitmap in CPU-accessible memory, making getImageData() faster at the cost of GPU compositing performance.

OffscreenCanvas for worker-based rendering:

const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);

// In worker:
const ctx = canvas.getContext('2d');
// Render in background thread
📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.