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

PropTypeDescription
picturesReactNode[]One picture per block, in order; with fewer pictures than blocks they repeat. Each fills the picture box (object-cover).
childrenReactNodeThe text column. Mark each block that should switch the picture with data-pic-block.
pictureClassNamestringSize of the picture box: width, aspect ratio (3/4 by default), radius.
containerRefObject<Element | null>Watch a scrolling box instead of the page: the picture switches as blocks pass the middle of that box.
classNamestringSpacing around the whole two-column layout.

Installation

npx shadcn@latest add https://purrfectui.com/r/sticky-crossfade.json

How 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.

0