JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 17
  • Score
    100M100P100Q53919F
  • License MIT

Fast WebP (static and animated) image processing in the browser using WebAssembly

Package Exports

  • magic-webp
  • magic-webp/pkg/magic_webp.mjs

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (magic-webp) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

🎨 magic-webp

Fast WebP image processing in the browser using WebAssembly

npm version License: MIT TypeScript WebAssembly

Process WebP images (static and animated) directly in the browser with native performance. Built on top of Google's libwebp compiled to WebAssembly.

🎮 Live DemoFeaturesInstallationQuick StartAPIDevelopment


✨ Features

  • 🖼️ WebP Support — Both static and animated WebP images
  • ✂️ Crop — Extract regions (preserves animation frames)
  • 📐 Resize — Multiple modes: cover, contain, fill, inside, outside
  • 🎚️ Quality Control — Adjustable output quality (0-100, lossless)
  • 🚀 Fast — Native libwebp with SIMD optimizations (5-10x faster)
  • 🌐 Browser-first — No server required, runs entirely client-side
  • 🔒 Thread-safe — Automatic operation queuing for concurrent calls
  • 📦 Zero dependencies — Pure WebAssembly, no external libraries

📦 Installation

npm install magic-webp
# or
pnpm add magic-webp
# or
yarn add magic-webp

🚀 Quick Start

Step 1: Copy worker.js to your public folder

# Copy from node_modules
cp node_modules/magic-webp/src-js/worker.ts public/worker.js

# Or download from GitHub
# https://github.com/medzhidov/magic-webp/blob/master/src-js/worker.ts

Step 2: Use the simple API

import { MagicWebpWorker } from 'magic-webp';

// Initialize worker
const webp = new MagicWebpWorker('/worker.js');

// Load image
const file = document.querySelector('input[type="file"]').files[0];
await webp.load(file);

// Resize (returns Blob directly!)
const blob = await webp.resize(400, 400, { mode: 'cover', quality: 75 });  // 75 = balanced (recommended)

// Use the result
const url = URL.createObjectURL(blob);
document.querySelector('img').src = url;

// Clean up when done
webp.terminate();

That's it! No manual Worker management, no message passing, just simple async calls.

✨ Benefits: Non-blocking UI, better performance, automatic request queuing

⚠️ Important: Web Worker Requirements

1. Same-Origin Policy

  • Worker file must be served from the same domain as your app
  • ❌ Won't work: new MagicWebpWorker('https://cdn.example.com/worker.js')
  • ✅ Works: new MagicWebpWorker('/worker.js') (same domain)

2. Module Type

  • Worker must be loaded as ES module (type: 'module')
  • Already handled by MagicWebpWorker constructor

3. CORS Headers (if serving from different path)

  • If worker is on subdomain, ensure proper CORS headers:
    Access-Control-Allow-Origin: *

4. File Serving

  • Worker file must be accessible via HTTP/HTTPS
  • ❌ Won't work with file:// protocol (local files)
  • ✅ Use local dev server: npx serve or python -m http.server

5. Build Tools

  • Vite: Worker is automatically bundled
    const webp = new MagicWebpWorker(
      new URL('./worker.ts', import.meta.url).href
    );
  • Webpack: Use worker-loader or native Worker support
  • Create React App: Place worker in public/ folder

Common Issues:

// ❌ WRONG: Cross-origin
const webp = new MagicWebpWorker('https://cdn.com/worker.js');
// Error: Failed to construct 'Worker': Script at '...' cannot be accessed from origin '...'

// ✅ CORRECT: Same origin
const webp = new MagicWebpWorker('/worker.js');

// ✅ CORRECT: Relative path
const webp = new MagicWebpWorker('./worker.js');

// ✅ CORRECT: Vite/Webpack (bundled)
const webp = new MagicWebpWorker(
  new URL('./worker.ts', import.meta.url).href
);

Alternative: Main Thread (Simpler, but blocks UI)

import { MagicWebp } from 'magic-webp';

const file = document.querySelector('input[type="file"]').files[0];
const img = await MagicWebp.fromBlob(file);
const resized = await img.resize(400, 400, { mode: 'cover', quality: 75 });  // 75 = balanced
const blob = resized.toBlob();

⚠️ Note: Main thread usage blocks the UI during processing. Use MagicWebpWorker for production apps.

📖 API

import { MagicWebpWorker } from 'magic-webp';

// Initialize
const webp = new MagicWebpWorker('/worker.js');

// Load image
await webp.load(file);  // File or Blob

// Get dimensions
console.log(webp.width, webp.height);

// Crop
const blob = await webp.crop(x, y, width, height, quality);

// Resize
const blob = await webp.resize(width, height, { mode, position, quality });

// Clean up
webp.terminate();

MagicWebp (Main Thread)

import { MagicWebp } from 'magic-webp';

// Load from File/Blob
const img = await MagicWebp.fromBlob(blob);
const img = await MagicWebp.fromFile(file);

// Load from URL
const img = await MagicWebp.fromUrl('https://example.com/image.webp');

// Load from Uint8Array
const img = await MagicWebp.fromBytes(uint8Array);

Transformations

All transformation methods are async and return Promise<MagicWebp>.

Crop

// Crop 200x200 region starting at (50, 50)
const cropped = await img.crop(50, 50, 200, 200, quality);

Resize

// Cover - fills dimensions, crops excess (default)
const cover = await img.resize(400, 400, { mode: 'cover' });

// Contain - fits inside dimensions, preserves aspect ratio
const contain = await img.resize(400, 400, { mode: 'contain' });

// Fill - stretches to exact dimensions (may distort)
const fill = await img.resize(400, 400, { mode: 'fill' });

// Inside - like contain, but never enlarges
const inside = await img.resize(400, 400, { mode: 'inside' });

// Outside - like cover, but never reduces
const outside = await img.resize(400, 400, { mode: 'outside' });

// With position (for cover/outside modes)
const banner = await img.resize(1200, 400, {
  mode: 'cover',
  position: 'top',  // 'center', 'top', 'bottom', 'left', 'right', etc.
  quality: 75       // 0-100, default 75 (balanced - recommended)
});

Output

// As Blob
const blob = img.toBlob();

// As Uint8Array
const bytes = img.toBytes();

// As Data URL
const dataUrl = await img.toDataUrl();

// As Object URL
const objectUrl = img.toObjectUrl();

Resize Options

interface ResizeOptions {
  mode?: 'cover' | 'contain' | 'fill' | 'inside' | 'outside';  // default: 'cover'
  position?: 'center' | 'top' | 'bottom' | 'left' | 'right' |  // default: 'center'
             'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
  quality?: number;  // 0-100, default: 75 (balanced)
}

💡 Quality Recommendations:

Quality File Size Visual Quality Use Case Recommended For
60-70 Smallest Visible artifacts Thumbnails, previews Low priority images
75-85 Medium Good balance Most web images Default (75)
90-95 Large Excellent Important photos Hero images, portfolios
100 Largest Perfect (lossless) Archival, editing When quality is critical

Quality Recommendations

  • 60-70: High compression, visible artifacts (good for thumbnails)
  • 75-85: Balanced quality/size (recommended for most cases)
  • 90-95: High quality, minimal artifacts (for important images)
  • 100: Lossless, largest file size (perfect quality preservation)

Supported Formats

Input:

  • ✅ Static WebP
  • ✅ Animated WebP (multi-frame)

Output:

  • ✅ Static WebP (from static input)
  • ✅ Animated WebP (from animated input, preserves all frames and timing)

Note: All operations (crop, resize) work on both static and animated WebP. For animated images, each frame is processed individually while preserving animation metadata (timing, loop count, etc.).

💡 Examples

import { MagicWebpWorker } from 'magic-webp';

const webp = new MagicWebpWorker('/worker.js');
await webp.load(file);

// Avatar - square 200x200, centered (high quality for profile pics)
const avatar = await webp.resize(200, 200, { mode: 'cover', quality: 85 });

// Product preview - fit inside 300x300 (balanced quality)
const preview = await webp.resize(300, 300, { mode: 'contain', quality: 75 });

// Banner - 1200x400, crop from top (high quality for hero images)
const banner = await webp.resize(1200, 400, {
  mode: 'cover',
  position: 'top',
  quality: 90
});

// Thumbnail - never enlarge (lower quality for small images)
const thumb = await webp.resize(150, 150, { mode: 'inside', quality: 65 });

// Crop specific region (balanced quality)
const cropped = await webp.crop(50, 50, 200, 200, 75);

webp.terminate();

With Main Thread

import { MagicWebp } from 'magic-webp';

const img = await MagicWebp.fromBlob(file);

// Chaining operations
const result = await img
  .crop(100, 100, 400, 400)
  .then(cropped => cropped.resize(200, 200, { mode: 'contain' }));

// Concurrent processing (automatically queued)
const [avatar, thumb, banner] = await Promise.all([
  img.resize(200, 200, { mode: 'cover' }),
  img.resize(150, 150, { mode: 'inside' }),
  img.resize(1200, 400, { mode: 'cover', position: 'top' })
]);

Animated WebP

// Works with both static and animated WebP
// For animated images, all frames are processed while preserving timing

const webp = new MagicWebpWorker('/worker.js');
await webp.load(animatedWebpFile);

// All frames will be resized
const resized = await webp.resize(400, 400, { mode: 'cover' });

🔧 Advanced Usage

Memory Management

// Worker automatically manages memory, but you should terminate when done
const webp = new MagicWebpWorker('/worker.js');

// ... use worker ...

// Clean up when component unmounts or app closes
webp.terminate();

Debug Mode

By default, magic-webp runs silently in production (no console logs). Enable debug mode for development:

import { setDebugMode } from 'magic-webp';

// Enable debug logging (disabled by default)
setDebugMode(true);

// Now you'll see detailed logs:
// [magic-webp] Loading Emscripten WASM module...
// [magic-webp] WASM module ready
// [magic-webp] Processing 45678 bytes
// [magic-webp] Cropping: 0,0 200x200, quality: 75
// etc.

// Disable debug mode
setDebugMode(false);

💡 Tip: Enable debug mode only during development. In production, logs are automatically disabled for better performance and cleaner console.

Error Handling

const webp = new MagicWebpWorker('/worker.js');

try {
  await webp.load(file);
  const blob = await webp.resize(400, 400, { mode: 'cover' });
} catch (error) {
  console.error('Processing failed:', error);
  // Handle error (show message to user, retry, etc.)
}

Multiple Workers (Parallel Processing)

// Create multiple workers for parallel processing
const workers = [
  new MagicWebpWorker('/worker.js'),
  new MagicWebpWorker('/worker.js'),
  new MagicWebpWorker('/worker.js')
];

// Process multiple images in parallel
const results = await Promise.all(
  files.map((file, i) => {
    const worker = workers[i % workers.length];
    return worker.load(file).then(() =>
      worker.resize(400, 400, { mode: 'cover' })
    );
  })
);

// Clean up
workers.forEach(w => w.terminate());

Browser Compatibility

  • ✅ Chrome 80+
  • ✅ Firefox 79+
  • ✅ Safari 14+
  • ✅ Edge 80+
  • ❌ IE 11 (no WebAssembly support)

Check before using:

if (typeof WebAssembly === 'undefined') {
  console.error('WebAssembly not supported');
  // Fallback to server-side processing
}

if (typeof Worker === 'undefined') {
  console.warn('Web Workers not supported, using main thread');
  // Use MagicWebp instead of MagicWebpWorker
}

🛠️ Development

Prerequisites

  • Node.js 18+
  • pnpm (recommended) or npm
  • Emscripten SDK (automatically installed)

Setup

# Clone repository
git clone https://github.com/medzhidov/magic-webp.git
cd magic-webp

# Install dependencies
pnpm install

# Build WASM module
pnpm build:wasm

# Run demo
pnpm demo:watch

Project Structure

magic-webp/
├── src-c/           # C source code
│   ├── animation.c  # WebP animation processing
│   └── magic_webp.c # Core functions
├── src-js/          # TypeScript API
│   ├── index.ts     # Main API
│   └── *.test.ts    # Tests
├── demo/            # Demo application
│   ├── index.html
│   ├── main.ts
│   └── worker.ts    # Web Worker for processing
├── tests/           # Native C tests
├── libwebp/         # Google's libwebp (submodule)
└── pkg/             # Built WASM output

Build Commands

# Build WASM module
pnpm build:wasm

# Run TypeScript tests
pnpm test

# Run native C tests
pnpm test:native

# Run demo (dev server)
pnpm demo:watch

# Build demo for production
pnpm demo:build

How It Works

  1. C Code (src-c/) - Uses libwebp's WebPPicture API for high-quality image processing
  2. Animation Support - Processes each frame individually, preserving timing and metadata
  3. Emscripten - Compiles C code to WebAssembly with SIMD optimizations
  4. TypeScript API (src-js/) - Provides clean, type-safe interface
  5. Operation Queue - Ensures thread-safety by serializing WASM calls
  6. Web Worker (demo) - Keeps UI responsive during processing

Performance

  • 5-10x faster than pure JavaScript implementations
  • SIMD optimizations (SSE2, NEON) for resize operations
  • Optimized cover mode - single pass resize+crop (2x faster)
  • Minimal memory - in-place operations where possible

📋 Quick Reference

Resize Modes

Mode Behavior Use Case
cover Fills dimensions, crops excess Avatars, thumbnails
contain Fits inside, preserves aspect Product images, previews
fill Stretches to exact size Backgrounds (may distort)
inside Like contain, never enlarges Thumbnails of small images
outside Like cover, never reduces Cropping large images

Position Options (for cover/outside)

Position Description
center Center of image (default)
top Top center
bottom Bottom center
left Left center
right Right center
top-left Top left corner
top-right Top right corner
bottom-left Bottom left corner
bottom-right Bottom right corner

Quality Guidelines

Quality File Size Visual Quality Use Case
60-70 Smallest Visible artifacts Thumbnails, previews
75-85 Medium Good balance Most web images
90-95 Large Excellent Important images, photos
100 Largest Perfect (lossless) Archival, editing

📄 License

MIT © Ilia Medzhidov

🙏 Acknowledgments


Made with ❤️ for the web