@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, thewidth/heightcontract.- 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.filterworkaround, 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:
- Fetches and decodes the image once per
srcchange. - Posts the pixel buffer to a shared Web Worker (one instance
per page, shared across every
<DitheredImage />you render). - Renders the dithered result to a
<canvas>that is pre-sized fromwidthandheightso there's no layout shift. - 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
widthandheightmatching 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.filterinOffscreenCanvasworkers, 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 setscrossOrigin="Anonymous"on the<img>used for decoding.