Sticky crossfade
A sticky picture beside a column of text that crossfades to the next picture as each block passes the middle of the screen.
Behaviour
- The picture sticks at the vertical middle of the scroll area beside the text, centred in a sticky box one root tall, so it never paints above the component; it shrinks to stay 2rem shorter than a short window.
- Each time a data-pic-block's top crosses the middle, the next picture fades in over 800ms on top while settling from 1.02 to 1 scale; the outgoing one stays opaque under it until then, so the box never shows through.
- Scrolling back across the middle before that fade ends fades the newer picture out on top of the one returning, so nothing snaps; a page loaded already scrolled shows its picture with no fade.
- One IntersectionObserver watches everything above the middle of the scroll root, so nothing runs per scroll event and a jump scroll (Home/End, an anchor) still lands on the right picture.
- Narrower than 48rem (a container query, not the viewport) it stacks and shows the first picture only, in flow.
- Reduced motion: a shorter 300ms plain crossfade with no scale.
Props
| Prop | Type | Description |
|---|---|---|
| pictures | ReactNode[] | One picture per block, in order; with fewer pictures than blocks they repeat. Each fills the picture box (object-cover). |
| children | ReactNode | The text column. Mark each block that should switch the picture with data-pic-block. |
| pictureClassName | string | Size of the picture box: width, aspect ratio (3/4 by default), radius. |
| container | RefObject<Element | null> | Watch a scrolling box instead of the page: the picture switches as blocks pass the middle of that box. |
| className | string | Spacing around the whole two-column layout. |
Installation
npx shadcn@latest add https://purrfectui.com/r/sticky-crossfade.jsonHow to use
import { StickyCrossfade } from "@/components/ui/sticky-crossfade";
<StickyCrossfade pictures={[<img key="a" src="/a.jpg" alt="" />, <img key="b" src="/b.jpg" alt="" />]}>
<p data-pic-block>First block: the first picture.</p>
<p data-pic-block>Second block: the picture changes as this passes the middle of the screen.</p>
</StickyCrossfade>
// inside a scrolling box: switch as blocks pass the middle of that box
const box = useRef<HTMLDivElement>(null);
<div ref={box} className="h-[600px] overflow-y-auto">
<StickyCrossfade container={box} pictures={pictures}>{blocks}</StickyCrossfade>
</div>Source code
Loading…
Credits
Ported from poinu.vercel.app.
License
MIT. Copy it into your project and change it as you like.