npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

image-resize-compress

v4.1.0

Published

Resize, compress, and convert images in the browser from a File, Blob, or URL - ~3 kB, zero runtime dependencies, TypeScript-first

Readme

image-resize-compress

npm version minzipped size CI License: MIT

Resize, compress, and convert images in the browser - from a File, Blob, or URL - in ~3 kB with zero runtime dependencies.

Live demo - drag in an image and try resizing, compression, format conversion, targetSize, and the worker option. Everything runs locally; nothing is uploaded. Source in demo/.

Why image-resize-compress?

| Feature | What you get | | -------------------- | ----------------------------------------------------------- | | Size | ~3 kB min+gzip | | Runtime dependencies | Zero | | Target file size | targetSize via binary search | | Flexible input | File, Blob, or URL (fromURL) | | Web worker | Opt-in, zero-config, with silent fallback | | Cancellation | Native AbortSignal support | | Progress | onProgress callback (0–100), works on the worker path too | | EXIF orientation | Handled natively via createImageBitmap | | Trustworthy releases | Provenance-signed, published from CI | | Tested for real | Full real-browser test suite (Playwright/Chromium) |

Small, dependency-free, and browser-native - it does resizing, compression, and format conversion, and nothing you don't need.

Installation

npm install image-resize-compress
# or: pnpm add image-resize-compress / yarn add image-resize-compress
import { fromBlob, fromURL, blobToURL, urlToBlob } from 'image-resize-compress';

Quickstart

All processing functions take an options object. Every field is optional.

import { fromBlob } from 'image-resize-compress';

// Convert to WebP at 80% quality, cap the longest edge at 1920px:
const out = await fromBlob(file, {
  quality: 80,
  maxWidthOrHeight: 1920,
  format: 'webp',
});

Headline feature: hit a target file size

Pass targetSize (in bytes) and the library binary-searches quality (jpeg/webp only, ≤ 8 encodes) to produce a blob at or under that size:

// Aim for ≤ 200 kB, keep dimensions:
const compressed = await fromBlob(file, {
  format: 'jpeg',
  targetSize: 200 * 1024,
});

It is best-effort: if the target is unreachable it resolves with the smallest blob it managed to produce. It never loops forever.

Off the main thread

Set worker: true to run decode→resize→encode in a Web Worker (via OffscreenCanvas), keeping the UI responsive:

const out = await fromBlob(bigFile, {
  format: 'webp',
  worker: true,
  quality: 75,
});
  • Opt-in and silent-fallback. If the environment lacks OffscreenCanvas, or a strict CSP blocks blob workers, it transparently runs on the main thread - same result, same errors. worker: true never rejects where worker: false would succeed.
  • CSP: a strict Content-Security-Policy needs worker-src blob: (or child-src blob:). Without it the library silently uses the main thread.
  • Worth it for images larger than ~5 MB and multi-file batches; pointless for thumbnails.
  • Abort behavior: aborting rejects the caller immediately with AbortError. An already in-flight worker job (e.g. a long targetSize search) still finishes internally before the next queued worker call starts - its late result is simply discarded.

API

fromBlob(blob, options?) → Promise<Blob>

Resize, compress, and/or convert a Blob or File.

| Option | Type | Default | Notes | | ------------------ | ---------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | quality | number (0–100] | encoder default | jpeg/webp only. Omit to avoid recompressing harder than needed. | | width | number \| 'auto' | 'auto' | Derived from height/original when 'auto'. | | height | number \| 'auto' | 'auto' | Derived from width/original when 'auto'. | | maxWidthOrHeight | number | - | Caps the longest edge, preserves aspect. Excludes width/height. | | fit | 'stretch' \| 'cover' | 'stretch' | 'cover' scales to fill then center-crops. Needs explicit width+height. | | format | 'png' \| 'jpeg' \| 'webp' | input format → png | Output format. | | backgroundColor | string (CSS color) | transparent | Flattens transparency onto this color. | | targetSize | number (bytes) | - | jpeg/webp only; binary-searches quality. | | signal | AbortSignal | - | Rejects with AbortError. | | onProgress | (progress: number) => void | - | Called with 0100. One terminal 100 for a plain resize; one call per targetSize step. Works on the worker path (relayed). | | worker | boolean | false | Off-main-thread, silent fallback (see above). |

Throws: TypeError (not a Blob), InvalidImageError (empty/undecodable), RangeError (bad quality/dimensions/targetSize, or targetSize with png), UnsupportedFormatError (bad format), ImageTooLargeError (pixel-count guard), EnvironmentError (not a browser), or AbortError (aborted).

fromURL(url, options?) → Promise<Blob>

Fetch an image from a URL, then process it. Accepts every ResizeOptions field plus fetchOptions?: RequestInit (headers, credentials, etc.). The server must allow CORS.

const blob = await fromURL('https://example.com/photo.jpg', {
  format: 'webp',
  maxWidthOrHeight: 1024,
  fetchOptions: { headers: { Authorization: 'Bearer …' } },
});

Throws: everything fromBlob throws, plus FetchError (network/CORS or a non-2xx response - carries .status for HTTP errors) and InvalidImageError when the URL returns a non-image response.

blobToURL(blob) → Promise<string>

Read a Blob/File into a data-URL string (handy for <img src> previews). No size cap.

const dataUrl = await blobToURL(resizedBlob);

urlToBlob(url, fetchOptions?) → Promise<Blob>

Fetch a URL and return the raw Blob (no processing).

const blob = await urlToBlob('https://example.com/photo.jpg');

Error classes

Typed errors are exported so you can branch with instanceof. All extend ImageProcessError. Abort rejects with a standard DOMException named AbortError; argument mistakes throw built-in TypeError/RangeError.

import {
  fromURL,
  ImageProcessError,
  InvalidImageError,
  UnsupportedFormatError,
  ImageTooLargeError,
  FetchError,
  EnvironmentError,
} from 'image-resize-compress';

try {
  await fromURL(url, { format: 'webp' });
} catch (err) {
  if (err instanceof FetchError) {
    console.error('fetch failed', err.status); // status set for HTTP errors
  } else if (err instanceof InvalidImageError) {
    console.error('not a usable image');
  } else if (err instanceof ImageProcessError) {
    console.error('processing failed', err.name);
  } else if (err.name === 'AbortError') {
    // cancelled - ignore
  }
}

Recipes

File input → preview

async function onChange(e) {
  const file = e.target.files[0];
  const resized = await fromBlob(file, {
    maxWidthOrHeight: 512,
    format: 'webp',
  });
  img.src = await blobToURL(resized);
}

Enforce a max upload size

const under1MB = await fromBlob(file, {
  format: 'jpeg',
  targetSize: 1024 * 1024,
});

Drive a progress bar

// Most granular during a targetSize search (one tick per binary-search step);
// a plain resize reports a single terminal 100.
const out = await fromBlob(file, {
  format: 'jpeg',
  targetSize: 200 * 1024,
  onProgress: (p) => {
    bar.value = p; // 0–100
  },
});

Square avatar (crop, not stretch)

// Scale to fill a 256x256 square, then center-crop the overflow -
// no distortion, unlike the default 'stretch'.
const avatar = await fromBlob(file, {
  width: 256,
  height: 256,
  fit: 'cover',
  format: 'webp',
});

Abort on component unmount (React)

useEffect(() => {
  const controller = new AbortController();
  fromBlob(file, { format: 'webp', signal: controller.signal })
    .then(setResult)
    .catch((err) => {
      if (err.name !== 'AbortError') throw err;
    });
  return () => controller.abort();
}, [file]);

HEIC input?

Not supported. Browsers cannot decode HEIC/HEIF via createImageBitmap or <img>, so there is nothing to resize. Detect it and tell the user to convert first (most phones can export JPEG):

const isHeic = /\.(heic|heif)$/i.test(file.name) || /hei[cf]/.test(file.type);
if (isHeic) {
  // Ask the user for a JPEG/PNG, or run a dedicated HEIC decoder before this.
}

SSR (Next.js, etc.)

This library runs only in the browser. Called during server rendering it throws EnvironmentError (instead of a cryptic document is not defined). Call it client-side - inside an effect, an event handler, or a 'use client' component.

Migrating to v4

Breaking: the deprecated positional signature is removed. fromBlob and fromURL now accept only (input, options?). Callers still on the v2/v3 positional form must switch to the options object — the mapping is identical to the v3 table below. Extra positional arguments are now silently ignored rather than mapped, so migrate any remaining positional calls.

Migrating to v3

v3 replaced the positional arguments with an options object. (In v3 the old positional signature still worked with a deprecation warning; it is gone as of v4 — see above.)

| v2 (positional) | v3+ (options object) | | --------------------------------------------------- | ------------------------------------------------------------------------- | | fromBlob(file, 80, 'auto', 'auto', 'webp') | fromBlob(file, { quality: 80, format: 'webp' }) | | fromBlob(file, 80, 200, 'auto', 'jpeg') | fromBlob(file, { quality: 80, width: 200, format: 'jpeg' }) | | fromURL(url, 75, 200, 'auto', 'webp') | fromURL(url, { quality: 75, width: 200, format: 'webp' }) | | fromBlob(file, 90, 'auto', 'auto', 'png', '#fff') | fromBlob(file, { quality: 90, format: 'png', backgroundColor: '#fff' }) |

Breaking: quality is now always 0–100. In v2, values below 1 were treated as a 0–1 fraction (0.8 meant 80%). In v3's options API there is no dual scale - quality is passed straight through as quality / 100, so 0.8 now means 0.8%, not 80%. A v2 caller who wrote 0.8 for 80% must write 80. (Values ≥ 1 are unchanged: 50 = 50%, 1 = 1%.)

Breaking: bmp and gif output removed. No major browser can encode these via canvas.toBlob; v2 silently produced PNG bytes mislabeled with the requested mime type. v3 throws UnsupportedFormatError instead of lying. Use png, jpeg, or webp.

Other changes: blobToURL no longer has a 10 MB cap and always resolves to a string; decode/encode errors are now typed classes; EXIF orientation is applied automatically; canvas re-encoding strips EXIF/GPS metadata (a privacy feature).

CDN (VanillaJS)

The IIFE build exposes a global imageResizeCompress:

<script src="https://cdn.jsdelivr.net/npm/image-resize-compress/dist/index.global.js"></script>
<script>
  async function resize() {
    const file = document.querySelector('#fileInput').files[0];
    const blob = await imageResizeCompress.fromBlob(file, {
      quality: 75,
      format: 'webp',
    });
    console.log(blob);
  }
</script>
<input type="file" id="fileInput" onchange="resize()" />

Works on unpkg too (https://unpkg.com/image-resize-compress/dist/index.global.js).

Compatibility

Browser-only; no IE. Works on all evergreen browsers.

| Capability | Requirement | | ------------------ | ------------------------------------------------------------------------------------- | | Core decode/encode | createImageBitmap - or falls back to HTMLImageElement decode | | worker: true | OffscreenCanvas (Chrome, Firefox, Safari ≥ 16.4) - else silent main-thread fallback | | Everything | Must run in a browser; server-side use throws EnvironmentError |

License

MIT © Alef Duarte

Contributions welcome - see CONTRIBUTING.md and the Code of Conduct.