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.
On this page
Installation
Usage
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.
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.
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.
States
Props
| Prop | Type | Notes |
|---|---|---|
status | "queued" | "generating" | "done" | "error" | Required |
alt | string | Required. Describes the finished image |
src | string | Optional until done. A partial image while generating |
variant | "denoise" | "mosaic" | "scan" | Defaults to denoise |
progress | number | 0 to 1. Leave it out for indeterminate |
aspectRatio | "W:H" | number | Defaults to 1 |
queuePosition | number | Shown while queued. 1 means next |
eta | number | Seconds left, shown as 8 s or 1 min 20 s |
error | ReactNode | What went wrong and what to do. Has a default |
onRetry | () => void | Shows Retry on failure |
onCancel | () => void | Shows Cancel while queued or generating |
seed | number | Varies the placeholder pattern between loaders in a grid |
width, height | number | Intrinsic size, passed to the <img> |
How it draws
- One canvas and one
requestAnimationFrameloop 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-activeand--chatcn-fg-faintare 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 itsalt.
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.