Back to blog

Processing Images with HTML Canvas: Resize and Compress in the Browser

·Updated ·4 min read
javascriptcanvastutorialimages

Processing images on the client side is useful when you want to reduce upload payloads, maintain user privacy by avoiding server processing, or enable offline functionality. The HTML Canvas API makes this straightforward.

Update

This post has been updated to include WebP output, which now has full browser support and typically achieves 25-35% better compression than JPEG at equivalent quality.

The Canvas API Basics

The canvas element acts as a drawing surface. You can draw images onto it, manipulate them, and export the result in various formats.

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
 
// Load an image
const img = new Image();
img.onload = () => {
  canvas.width = img.width;
  canvas.height = img.height;
  ctx.drawImage(img, 0, 0);
};
img.src = imageUrl;

The drawImage method is the core of image manipulation. It can take source coordinates, destination coordinates, and scaling parameters.

Resizing Images

To resize an image, set the canvas dimensions to your target size and draw the image scaled to fit:

function resizeImage(img: HTMLImageElement, maxWidth: number, maxHeight: number): HTMLCanvasElement {
  let { width, height } = img;
 
  // Calculate new dimensions maintaining aspect ratio
  if (width > maxWidth) {
    height = (height * maxWidth) / width;
    width = maxWidth;
  }
  if (height > maxHeight) {
    width = (width * maxHeight) / height;
    height = maxHeight;
  }
 
  const canvas = document.createElement('canvas');
  canvas.width = width;
  canvas.height = height;
 
  const ctx = canvas.getContext('2d')!;
  ctx.imageSmoothingEnabled = true;
  ctx.imageSmoothingQuality = 'high';
  ctx.drawImage(img, 0, 0, width, height);
 
  return canvas;
}

The imageSmoothingEnabled and imageSmoothingQuality properties control interpolation when scaling. Setting quality to 'high' produces smoother results for downscaling.

Compressing Images

Canvas provides two methods for exporting: toDataURL and toBlob. Both accept a MIME type and quality parameter:

function compressImage(
  canvas: HTMLCanvasElement,
  format: 'image/jpeg' | 'image/webp' | 'image/png',
  quality: number,
): Promise<Blob> {
  return new Promise((resolve, reject) => {
    canvas.toBlob(
      (blob) => {
        if (blob) resolve(blob);
        else reject(new Error('Failed to create blob'));
      },
      format,
      quality, // 0.0 to 1.0, ignored for PNG
    );
  });
}

The quality parameter ranges from 0 to 1. Lower values mean more compression and smaller files. PNG is lossless and ignores this parameter. JPEG and WebP both support lossy compression WebP typically achieves better compression at equivalent quality.

Handling File Uploads

To process user-uploaded images, use the FileReader API or object URLs:

function loadImageFromFile(file: File): Promise<HTMLImageElement> {
  return new Promise((resolve, reject) => {
    const url = URL.createObjectURL(file);
    const img = new Image();
 
    img.onload = () => {
      URL.revokeObjectURL(url); // Clean up
      resolve(img);
    };
    img.onerror = () => {
      URL.revokeObjectURL(url);
      reject(new Error('Failed to load image'));
    };
 
    img.src = url;
  });
}

Memory Management

Always revoke object URLs when done with them. For large images, this prevents memory leaks. Process images one at a time rather than batching to keep memory usage reasonable.

Putting It All Together

Here's a complete processing pipeline:

async function processImage(
  file: File,
  options: {
    maxWidth: number;
    maxHeight: number;
    format: 'image/jpeg' | 'image/webp' | 'image/png';
    quality: number;
  },
): Promise<{ blob: Blob; dataUrl: string }> {
  // Load the image
  const img = await loadImageFromFile(file);
 
  // Resize
  const canvas = resizeImage(img, options.maxWidth, options.maxHeight);
 
  // Compress and export
  const blob = await compressImage(canvas, options.format, options.quality);
  const dataUrl = canvas.toDataURL(options.format, options.quality);
 
  return { blob, dataUrl };
}
 
// Usage
const { blob, dataUrl } = await processImage(file, {
  maxWidth: 1200,
  maxHeight: 1200,
  format: 'image/webp',
  quality: 0.8,
});

Try It Yourself

Here's a fully functional image processor. Upload an image, adjust the settings, and download the result:

Drop an image here or click to upload

Supports JPEG, PNG, WebP (max 10MB)

Browser Compatibility

Canvas is well-supported across browsers. The main consideration is WebP output while modern browsers support it, you may want to fall back to JPEG for older browsers. You can detect support like this:

function supportsWebP(): Promise<boolean> {
  return new Promise((resolve) => {
    const canvas = document.createElement('canvas');
    canvas.width = 1;
    canvas.height = 1;
    const dataUrl = canvas.toDataURL('image/webp');
    resolve(dataUrl.startsWith('data:image/webp'));
  });
}

Going Further

The Canvas API enables more advanced operations:

  • Filters: Manipulate pixel data directly with getImageData and putImageData for grayscale, brightness, contrast adjustments
  • Cropping: Use the source parameters of drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh) to crop regions
  • Watermarking: Draw text or overlay images with fillText or additional drawImage calls
  • Rotation: Use ctx.rotate() with ctx.translate() for image rotation

For detailed API reference, see the MDN Canvas documentation.