Create a radial vignette effect on a canvas

by Arjan Haverkamp

JavaScript

/**
 * Creates a canvas element of given size with an elliptical radial vignette effect.
 * @param {number} width - The width of the canvas in pixels.
 * @param {number} height - The height of the canvas in pixels.
 * @param {number} innerPercent - Fraction (0–1) of the dimensions defining the unshaded area's diameter.
 *                                e.g. 0.5 means 50% of width for the horizontal axis and 50% of height for the vertical axis.
 * @returns {HTMLCanvasElement} A canvas element with the vignette drawn.
 */
function createVignetteCanvas(width, height, innerPercent) {
  // Clamp innerPercent between 0 and 1
  const pct = Math.max(0, Math.min(innerPercent, 1));

  // Create and size the canvas
  const canvas = document.createElement('canvas');
  canvas.width = width;
  canvas.height = height;
  const ctx = canvas.getContext('2d');
  if (!ctx) throw new Error('Canvas 2D context not available');

  // Center coordinates
  const cx = width / 2;
  const cy = height / 2;

  // Radii for full canvas bounds
  const rx1 = width / 2;
  const ry1 = height / 2;

  // Use context transformation to draw an elliptical gradient
  ctx.save();
  ctx.translate(cx, cy);
  // Scale a unit circle into an ellipse matching the canvas aspect ratio
  ctx.scale(rx1, ry1);

  // In scaled space: inner radius is pct (unit circle fraction), outer radius √2 reaches corners
  const r0 = pct;
  const r1 = Math.SQRT2;

  const gradient = ctx.createRadialGradient(0, 0, r0, 0, 0, r1);
  gradient.addColorStop(0, 'rgba(0,0,0,0)');
  gradient.addColorStop(1, 'rgba(0,0,0,1)');

  ctx.fillStyle = gradient;
  // Fill a 2×2 square in scaled coordinates, which maps to the full canvas
  ctx.fillRect(-1, -1, 2, 2);
  ctx.restore();

  return canvas;
}

// Example usage:
const vignetteCanvas = createVignetteCanvas(400, 800, 0.9);  // Elliptical clear area at 50% of each dimension
document.body.appendChild(vignetteCanvas);