Skip to content
Sign in
MediaImage loaderMenu

Image loader

The frame an image generation renders into: a live placeholder while the model works, then the reveal. Three variants on one canvas (denoise, mosaic and scan), driven by progress or running indeterminate, in token colors, from 64 px thumbnails to full width.

One generation from the queue to the reveal, then again. Switch between denoise, mosaic and scan while it runs.

Installation

$ pnpm dlx shadcn@latest add https://chatcn.dev/r/image-loader.json

Usage

tsx
import { ImageLoader } from "@/components/ui/chatcn/image-loader"

<ImageLoader
  status={job.status}        // "queued" | "generating" | "done" | "error"
  progress={job.progress}    // 0 to 1, or leave it out
  src={job.url}              // optional until done
  alt={prompt}
  aspectRatio="4:3"
/>

Set aspectRatio from the request, so the frame has its final shape from the first paint and nothing shifts when the image lands. When the provider streams partial images, pass each one as src while generating and the placeholder resolves into it. When it sends nothing until the end, leave src out: the placeholder carries the wait on its own and the reveal plays once the finished image has decoded.

Pass progress when the provider reports it. Without it the loader runs indeterminate: the denoise grain keeps moving, the mosaic twinkles and the scan band loops, but nothing pretends to be further along than it is.

Examples

Variants

denoise is the default: coarse monochrome grain that gets finer in discrete steps, like the preview a diffusion model shows. mosaic lays the frame out in the kit's rounded squares, which settle from coarse blocks to fine ones and then crossfade into the image. scan moves a soft edge down the frame, with the resolved picture above it and a 1-bit dither below.

Denoise, mosaic and scan side by side, each looping through a full generation.

Partial images

Each new src is decoded off screen first, so the previous preview stays until the next one can be drawn. The canvas only ever draws the image and never reads its pixels, so images from another origin work without CORS headers.

A provider that streams partial images: each preview is passed as src and the placeholder resolves into it.

Thumbnails to full width

The chrome follows the frame width through container queries. Under 160 px the status pill and Cancel fold into a 2 px progress line (or a small clock while queued), and failure becomes a single Retry button. The estimate appears from 260 px.

64 px thumbnails fold the chrome into a 2 px progress line; a 16:9 frame shows the full status.

States

Queued, indeterminate, determinate with an estimate, failed, restored from history and missing file.

Props

PropTypeNotes
status"queued" | "generating" | "done" | "error"Required
altstringRequired. Describes the finished image
srcstringOptional until done. A partial image while generating
variant"denoise" | "mosaic" | "scan"Defaults to denoise
progressnumber0 to 1. Leave it out for indeterminate
aspectRatio"W:H" | numberDefaults to 1
queuePositionnumberShown while queued. 1 means next
etanumberSeconds left, shown as 8 s or 1 min 20 s
errorReactNodeWhat went wrong and what to do. Has a default
onRetry() => voidShows Retry on failure
onCancel() => voidShows Cancel while queued or generating
seednumberVaries the placeholder pattern between loaders in a grid
width, heightnumberIntrinsic size, passed to the <img>

How it draws

  • One canvas and one requestAnimationFrame loop per loader, capped near 30 fps. It pauses when the frame scrolls out of view (IntersectionObserver) and when the tab is hidden, and stops on its own once there is nothing left to move.
  • Colors come from the theme: --chatcn-fill, --chatcn-fill-active and --chatcn-fg-faint are read from the canvas element at runtime and read again when the theme class, the color scheme or a scoped theme changes.
  • The bitmap follows the frame size and device pixel ratio, up to 2048 px on the long side. Noise and dither buffers are capped, so a 1024 px frame costs about what a 400 px one does.
  • The finished image is a real <img> laid over the canvas, so it stays sharp, selectable by assistive tech and keeps its alt.

Accessibility

  • While queued or generating, a progress bar carries the state for screen readers, for example 64%, about 9 s left, even when the visible pill has folded away on a thumbnail.
  • A polite live region says Image ready once when the reveal finishes, or Image generation failed. A frame restored from history says nothing.
  • Failure pairs the red icon with words and a Retry button. Cancel and Retry grow to 44 px on touch screens.
  • The status pill sits on an opaque surface, so it stays legible over any pattern or partial image.

Motion

  • Denoise reseeds its grain ten times a second and steps through 20 resolutions as progress grows. Mosaic levels settle with a 240 ms crossfade. Scan eases its edge toward the reported progress, so jumps from the provider read as movement.
  • On completion the placeholder resolves to full detail over 420 ms, then crossfades into the image over 300 ms.
  • Under reduced motion nothing moves: each progress update draws one still frame, and the image arrives with a 150 ms opacity crossfade.