PixConvertPixConvertBlog

PixConvertBlog

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

QualityUse caseApprox size vs JPG
100 (lossless)Logos, icons, UI~same as PNG
90-95High-fidelity web images~15-20% smaller
80-90 ⭐Most website photos~25-35% smaller
60-80Thumbnails, previews~40-55% smaller