ditherkit
@ditherkit/react

@ditherkit/react

Client-side dithering component with Web Worker rendering, built for interactive use.

@ditherkit/react is a single component, <DitheredImage />, that processes images in a Web Worker and renders the result to a canvas. It's designed for interactive use cases — real-time slider adjustments, live previews, client-side uploads — where the dithering needs to run in the browser and the UI needs to stay responsive.

If you don't need interactivity and just want dithered images served efficiently from a Next.js app, reach for @ditherkit/next instead.

Pages in this section

  • <DitheredImage /> — component reference: props, behavior, accessibility, the width/height contract.
  • Web Workers — how the worker is bundled, the shared-singleton architecture, request lifecycle, bundler compatibility notes. Read this if you want to understand how the component stays fast under rapid prop changes.
  • Recipes — Safari ctx.filter workaround, cross-origin images, error handling, dynamic palette swapping.

One-minute overview

import { DitheredImage } from '@ditherkit/react'

export function Portrait() {
  return (
    <DitheredImage
      src="/portrait.jpg"
      alt="A portrait, dithered"
      width={400}
      height={600}
      algorithm="Floyd-Steinberg"
      brightness={10}
      contrast={15}
    />
  )
}

That's the whole API surface. The component:

  1. Fetches and decodes the image once per src change.
  2. Posts the pixel buffer to a shared Web Worker (one instance per page, shared across every <DitheredImage /> you render).
  3. Renders the dithered result to a <canvas> that is pre-sized from width and height so there's no layout shift.
  4. Reprocesses on prop change (algorithm, brightness, palette, etc.) without refetching the image or recreating the worker.

When a slider change supersedes an in-flight request, the in-progress result is abandoned and only the latest settings reach the canvas — you get smooth updates under rapid prop changes. See Web Workers for how it all fits together.

What you need to know

  • Pass width and height matching your source image if you want zero layout shift. Pass either, both, or neither — the missing dimension is derived from the source's intrinsic aspect ratio after the image loads. See Sizing model for the full rules.
  • One shared worker per page. Adding more <DitheredImage /> instances is cheap — they all multiplex through the same worker.
  • Safari fallback is automatic. Safari doesn't support ctx.filter in OffscreenCanvas workers, so the component detects Safari at runtime and uses a pure-JS brightness/contrast path instead.
  • Cross-origin works as long as the image server sends Access-Control-Allow-Origin. The component sets crossOrigin="Anonymous" on the <img> used for decoding.

On this page