Processing Images with HTML Canvas: Resize and Compress in the Browser
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
getImageDataandputImageDatafor 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
fillTextor additionaldrawImagecalls - Rotation: Use
ctx.rotate()withctx.translate()for image rotation
For detailed API reference, see the MDN Canvas documentation.