JavaScriptNode.jsBrowser6 min read
Convert JPG/PNG to WebP in JavaScript
Two approaches: Canvas API for in-browser conversion, and sharp for Node.js server-side processing. Both preserve quality and support transparency.
Method 1: Canvas API (browser, no dependencies)
The Canvas API is available in all modern browsers and can encode to WebP natively. This is how PixConvert works under the hood.
/**
* Convert an image File to WebP using Canvas API.
* Works in Chrome, Firefox, Safari 14+, Edge.
* @param {File} file - Input image file (JPG, PNG, WebP, etc.)
* @param {number} quality - 0.0 to 1.0, default 0.92
* @returns {Promise<Blob>} WebP blob
*/
async function convertToWebP(file, quality = 0.92) {
return new Promise((resolve, reject) => {
const img = new Image();
const url = URL.createObjectURL(file);
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
URL.revokeObjectURL(url);
canvas.toBlob(
(blob) => {
if (!blob) return reject(new Error('Conversion failed'));
resolve(blob);
},
'image/webp',
quality
);
};
img.onerror = () => reject(new Error('Failed to load image'));
img.src = url;
});
}
// Usage
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
const webpBlob = await convertToWebP(file, 0.92);
// Download
const a = document.createElement('a');
a.href = URL.createObjectURL(webpBlob);
a.download = file.name.replace(/\.[^.]+$/, '') + '.webp';
a.click();
});Note: AVIF encoding (
image/avif) via canvas.toBlob only works in Chrome 94+ and Edge 94+. Firefox and Safari do not support AVIF encoding via the Canvas API.Method 2: Checking WebP support
Always check if the browser supports WebP encoding before using it as output:
/**
* Check if the browser can encode to a given MIME type via canvas.toBlob()
* @param {string} mimeType - e.g. 'image/webp', 'image/avif'
* @returns {Promise<boolean>}
*/
async function canEncode(mimeType) {
return new Promise((resolve) => {
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
canvas.toBlob(
(blob) => resolve(blob !== null && blob.type === mimeType),
mimeType
);
});
}
// Usage
const supportsWebP = await canEncode('image/webp'); // true in all modern browsers
const supportsAVIF = await canEncode('image/avif'); // true in Chrome 94+, Edge 94+Method 3: Node.js with sharp (server-side)
sharp is the fastest Node.js image processing library. It wraps libvips and supports WebP, AVIF, PNG, and JPEG.
npm install sharp
import sharp from 'sharp';
import { readdir } from 'fs/promises';
import path from 'path';
// Single file
await sharp('input.jpg')
.webp({ quality: 92 })
.toFile('output.webp');
// With transparency (PNG source)
await sharp('logo.png')
.webp({ lossless: true }) // lossless preserves sharp edges
.toFile('logo.webp');
// Batch convert entire directory
async function batchConvertToWebP(dir, quality = 85) {
const files = await readdir(dir);
const images = files.filter(f => /\.(jpg|jpeg|png)$/i.test(f));
await Promise.all(
images.map(async (file) => {
const input = path.join(dir, file);
const output = path.join(dir, file.replace(/\.[^.]+$/, '.webp'));
await sharp(input).webp({ quality }).toFile(output);
console.log(`✓ ${file} → ${path.basename(output)}`);
})
);
}
await batchConvertToWebP('./public/images');Method 4: imagemin (build pipeline)
For integrating into a build process (not Next.js — see the Next.js/Vite guide for that):
npm install imagemin imagemin-webp
import imagemin from 'imagemin';
import imageminWebp from 'imagemin-webp';
await imagemin(['images/*.{jpg,png}'], {
destination: 'images/webp',
plugins: [
imageminWebp({ quality: 85 })
],
});
console.log('Images converted to WebP');Quality guide
| Quality | Use case | Approx size vs JPG |
|---|---|---|
| 100 (lossless) | Logos, icons, UI | ~same as PNG |
| 90-95 | High-fidelity web images | ~15-20% smaller |
| 80-90 ⭐ | Most website photos | ~25-35% smaller |
| 60-80 | Thumbnails, previews | ~40-55% smaller |