Installation
pnpm dlx shadcn@latest add https://dithercn.dev/r/scroll-reveal-image.jsonUsage
import { ScrollRevealImage } from "@/components/dithercn/scroll-reveal-image";
export function Chapter() {
return (
<ScrollRevealImage
src="/photos/launch.jpg"
alt="The launch pad at dawn"
className="aspect-video w-full rounded-xl"
/>
);
}Progress follows the scroll position, not a timer. When the top of the image enters at the bottom of the viewport it is a few large blocks. It sharpens in nine steps to full dither detail, then the photo dissolves in cell by cell. It is finished when its top edge is 30% down the viewport. Scroll back and it runs in reverse.
start and finish move those two points, as a share of the viewport height from the top. Inside a scrolling panel, pass the panel's ref as scrollRoot, as the demo above does.
Examples
In a long read
The image resolves as the reader reaches it. finish={0.4} completes it a little lower on the screen.
Ending on dither
end="dither" stops at full dither detail and never shows the photo. A later start on the second image staggers the pair.
Props
Takes every look prop of Dither image (preset, color, palette, method, cell, contrast, brightness, normalize, sharpen, invert, fit), plus:
| Prop | Type | Default |
|---|---|---|
| src* Image URL. Cross-origin images need CORS headers. | Type: string | Default – |
| alt* Describes the image. Pass "" when it is decorative. | Type: string | Default – |
| end Finish on the original photo, or on the finest dither. | Type: "photo" | "dither" | Default "photo" |
| coarsest How many cells wide the first blocks are. | Type: number | Default 24 |
| start Where the image's top edge is when the reveal starts, 0 (top) to 1 (bottom of the viewport). | Type: number | Default 1 |
| finish Where the top edge is when the reveal finishes. | Type: number | Default 0.3 |
| scrollRoot The scrolling element, when it is not the page. | Type: RefObject<HTMLElement | null> | Default – |
| progress Set the progress yourself, 0 to 1, instead of following the scroll. | Type: number | Default – |
| onProgressChange Called when the progress changes. | Type: (progress: number) => void | Default – |
| ratio Width over height. | Type: number | Default The image's ratio |
Notes
- Under reduced motion the image is shown finished: the photo, or the fine dither with
end="dither". - The root has
role="img"and youralttext as its label, so screen readers hear the image once, whatever stage it is at.data-stageiscoarse,fineorphoto. - Each coarseness is dithered once and cached, so scrolling back and forth costs nothing after the first pass. Scroll handling only runs while the image is near the viewport, at most once per frame.