Compressly: Client-Side Image Compression & Resize in JavaScript

Category: Image , Javascript | October 15, 2026
AuthorMostafashadow1
Last UpdateOctober 15, 2026
LicenseMIT
Views0 views
Compressly: Client-Side Image Compression & Resize in JavaScript

Compressly is a JavaScript library for resizing and compressing image files directly in the browser before upload.

It handles individual photos or batches, lets you control image dimensions and encoding quality, and returns File objects for your existing upload code.

The library can encode images in a Web Worker when browser capabilities permit, and an optional React hook exposes progress and results.

Features

  • Proportional downscaling for oversized images.
  • JPEG, WebP, and PNG output formats.
  • Native image orientation handling for camera photos.
  • Automatic pass-through for files that meet the size and dimension limits.
  • Progress updates with processing stages.
  • Concurrent batch jobs with per-file completion callbacks.
  • Detailed compression results with file sizes, reduction percentages, and processing times.

How To Use It

Installation

Install Compressly through npm for vanilla JavaScript projects with a build tool or for framework applications.

npm install compressly

For a page that loads JavaScript modules directly from a CDN, use the browser module below.

<script type="module">
  import { compressImage } from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/index.js';
  // Call compressImage() after obtaining a File from an input or drop zone.
</script>

Basic Usage

Use an image file input and pass its selected File to compressImage(). The promise resolves to a File, which can be uploaded, previewed, or downloaded.

<input id="photo-input" type="file" accept="image/*">
import { compressImage } from 'compressly';
const input = document.querySelector('#photo-input');
input.addEventListener('change', async () => {
  const file = input.files?.[0];
  if (!file) return;
  const result = await compressImage(file, {
    maxWidth: 1600,
    maxHeight: 1600,
    quality: 0.8,
  });
  console.log(`${file.size} bytes -> ${result.size} bytes`);
});

Compress Multiple Images

compressImages() takes a File[] and returns the processed files in their original order. A multi-file input supplies a FileList, which needs conversion to an array.

<input id="photo-batch" type="file" accept="image/*" multiple>
import { compressImages } from 'compressly';
const picker = document.querySelector('#photo-batch');
picker.addEventListener('change', async () => {
  const files = Array.from(picker.files ?? []);
  if (!files.length) return;
  const results = await compressImages(files, {
    concurrency: 2,
    maxWidth: 1600,
    onBatchProgress(percent, completed, total) {
      console.log(`${completed}/${total} complete (${percent}%)`);
    },
  });
  console.log(results);
});

Enforce an Upload Limit

targetSizeMB asks Compressly to reduce the encoded file below a specified size. Check result.file.size before uploading to an endpoint with a strict limit.

import { compressImageWithDetails } from 'compressly';
const input = document.querySelector('#photo-input');
input.addEventListener('change', async () => {
  const file = input.files?.[0];
  if (!file) return;
  const limitBytes = 500 * 1024;
  const result = await compressImageWithDetails(file, {
    targetSizeMB: limitBytes / (1024 * 1024),
    mimeType: 'image/jpeg',
  });
  if (result.file.size > limitBytes) {
    console.error('The processed image exceeds the upload limit.');
    return;
  }
  const form = new FormData();
  form.append('photo', result.file);
  await fetch('/api/photos', { method: 'POST', body: form });
});

Convert an Image to WebP

Small files that already meet the default dimension and file-size limits pass through unchanged. Set forceResize: true when an existing image must be re-encoded into a requested output format.

import { compressImage } from 'compressly';
async function convertToWebP(file) {
  return compressImage(file, {
    mimeType: 'image/webp',
    forceResize: true,
    quality: 0.8,
  });
}

React Integration

React apps can use useImageCompressor() from compressly/react. This example receives a selected photo, starts compression, and displays the resulting file size. Use it in a client component in frameworks that render components on the server.

import { useImageCompressor } from 'compressly/react';
export function AvatarUploader() {
  const { compress, isCompressing, progress, lastResult, error } =
    useImageCompressor({ maxWidth: 640, maxHeight: 640 });
  async function handleChange(event) {
    const file = event.currentTarget.files?.[0];
    if (!file) return;
    try {
      await compress(file);
    } catch {
      // Errors from rejected operations appear in the hook's error state.
    }
  }
  return (
    <div>
      <input type="file" accept="image/*" onChange={handleChange} disabled={isCompressing} />
      {isCompressing && <p>Compressing: {progress}%</p>}
      {lastResult && <p>Processed size: {Math.round(lastResult.compressedSize / 1024)} KiB</p>}
      {error && <p role="alert">{error.message}</p>}
    </div>
  );
}

All Configuration Options

  • maxSizeMB (number, default 1): Size threshold in MiB. An eligible file at or below the threshold can pass through unchanged when it also meets the dimension limits.
  • targetSizeMB (number, optional): Requests a maximum output size in MiB. Attempts up to four encodes, adjusting quality and sometimes dimensions. The limit is not guaranteed.
  • maxWidth (number, default 1920): Maximum width in pixels, with aspect ratio preserved.
  • maxHeight (number, default 1920): Maximum height in pixels, with aspect ratio preserved.
  • quality (number, default 0.82): Initial JPEG or WebP encoding quality, normally from 0.1 to 1. PNG encoding does not use the quality value.
  • mimeType ('image/jpeg' | 'image/webp' | 'image/png', optional): Requests the output MIME type. PNG inputs use PNG by default. Other raster images use JPEG by default. Files skipped by the size check keep their original format.
  • autoRotate (boolean, default true): Requests EXIF orientation handling during image decoding. Actual orientation handling also depends on the browser’s native image APIs.
  • useWorker (boolean, default true): Attempts encoding in a Web Worker with OffscreenCanvas when available. Uses regular Canvas when Worker encoding cannot run.
  • forceResize (boolean, default false): Forces raster image processing even when the input already meets the size and dimension limits.
  • onProgress ((progress: number, phase: CompressionPhase) => void, optional): Receives processing milestones from 0 to 100 and a compression phase. It is not a network upload progress callback.

Batch Configuration

BatchCompressOptions extends CompressOptions with three properties.

  • concurrency (number, default 3): Number of concurrent image jobs. Supply a positive integer.
  • onFileComplete ((result: CompressResult, index: number, total: number) => void, optional): Called after each file finishes. index uses the original zero-based file position.
  • onBatchProgress ((overallPercentage: number, completedCount: number, totalCount: number) => void, optional): Called after a file completes with the overall batch percentage and counts.

Compression Functions

The main compressly entry point exposes four compression functions. All accept browser File inputs. The regular variants return files, and the WithDetails variants return result objects.

compressImage(file: File, options?: CompressOptions): Promise<File>
compressImageWithDetails(file: File, options?: CompressOptions): Promise<CompressResult>
compressImages(files: File[], options?: BatchCompressOptions): Promise<File[]>
compressImagesWithDetails(files: File[], options?: BatchCompressOptions): Promise<CompressResult[]>

Compression Result Reference

CompressResult contains these six public fields. An image that bypasses processing returns the original File. If the library cannot decode or compress a file, it can return the original file with reductionPercentage set to 0.

  • file (File): Returned image file.
  • originalSize (number): Input size in bytes.
  • compressedSize (number): Returned file size in bytes.
  • reductionPercentage (number): Calculated percentage reduction, with negative results reported as 0.
  • timeTakenMs (number): Processing duration in milliseconds.
  • dimensions ({ width: number, height: number }): Width and height reported by the compression operation. Both are 0 when dimensions cannot be determined.

Progress Phases

CompressionPhase is a TypeScript union of idle, reading, orienting, resizing, compressing, and complete. The React hook uses idle as its initial state. The compression functions report milestones through onProgress.

Other Public Exports

In addition to the four compression functions, the main package exports three utility functions and the associated TypeScript types. Call close() on LoadedImageSource after working with a loaded image to release its resources.

  • calculateDimensions(currentWidth: number, currentHeight: number, maxWidth: number, maxHeight: number): Returns { width: number, height: number }. It scales down images to the specified bounds and preserves the aspect ratio.
  • loadImageWithExif(file: File, autoRotate: boolean = true): Returns Promise<LoadedImageSource> with source, width, height, and close(). The source is an ImageBitmap or an HTMLImageElement.
  • isWorkerSupported(): Returns a boolean based on the presence of Worker, OffscreenCanvas, and related browser APIs. Security policies can prevent actual Worker execution even when it returns true.

React Hook API

useImageCompressor(defaultOptions?: CompressOptions) exports the following values from compressly/react. It tracks the current operation and previous results.

  • compress(file: File, overrideOptions?: CompressOptions): Returns Promise<CompressResult> for one image.
  • compressMultiple(files: File[], overrideOptions?: BatchCompressOptions): Returns Promise<CompressResult[]> for a batch.
  • isCompressing (boolean): true during an active hook operation.
  • progress (number): Latest compression or batch percentage.
  • phase (CompressionPhase): Current processing phase.
  • lastResult (CompressResult | null): Most recent single-file result.
  • lastBatchResults (CompressResult[]): Most recent batch result array.
  • error (Error | null): Error captured when a hook operation rejects.
  • reset(): Resets stored status, progress, results, and error. It does not cancel work already in progress.

Alternatives & Related Resources

FAQs

Q: Can I compress an image inside a Next.js Server Component?
A: Image encoding needs browser APIs. Call the compression function from a client-side event handler. The package can be imported in server-rendered projects, but a server-side call does not perform browser compression.

Q: How does Compressly handle animated GIFs and SVG files?
A: It returns the original GIF or SVG File without compression. Validate File.type if your upload flow accepts only formats you plan to re-encode.

Q: Why can the result be larger than the original file?
A: Re-encoding can increase file size, especially when a size target forces another pass. Compare result.file.size against the original size before choosing which file to upload.

Q: Why does image processing run on the main thread even with useWorker: true?
A: Web Worker encoding requires Worker, OffscreenCanvas, and permission to create a Blob-based worker. When that cannot run, Compressly uses Canvas on the main thread. A site’s Content Security Policy can prevent worker creation.

You Might Be Interested In:


Leave a Reply