Component
Scroll Reveal Section
A scroll-scrubbed text section — headline words illuminate one-by-one as the user scrolls through. Up to four small photos (250×250) drift outward from the text center as the reveal plays, creating an editorial depth effect. Supports dark and light themes.
Preview
This component requires scroll interaction to animate — open the live preview to see it in action.
Open interactive preview →How it works
The component renders a 260vh outer container with a 100vh sticky inner. Framer Motion's useScroll tracks progress across the container (0 → 1 over ~160vh of scrolling), providing a MotionValue that each sub-component consumes independently — no re-renders.
Each word in the headline is wrapped in a <Word> sub-component that owns its own useTransform call — hooks can't be called inside a .map(). Each word illuminates across an overlapping 1.5× slice of the scroll range so the cascade feels fluid rather than mechanical.
Floating images each own a <FloatingImage> sub-component with separate x / y / rotate / scale / opacity transforms. They start invisible at center and fan out to their corner positions as scroll advances. Velocity is not seeded onto scale or opacity to prevent overshoot on small-range properties.
Animation timeline
| Element | Scroll range | Animation |
|---|---|---|
| Eyebrow | 0.01 → 0.10 | opacity 0 → 1 |
| Images (fan-out) | 0.04 → 0.52 | x/y/rotate from center to corner; scale 0.88 → 1 |
| Images (fade-in) | 0.04 → 0.22 | opacity 0 → 1 (faster than the drift) |
| Words (per-word) | staggered 0.08 → 0.70 | opacity 0.12 → 1, overlapping 1.5× slices |
| Body copy | 0.64 → 0.80 | opacity 0 → 1 |
Sanity fields
Available as scrollRevealSection in the Page builder. The Floating images field accepts up to 4 photos — square or portrait crops work best.
| Prop | Type | Default | Description |
|---|---|---|---|
| headline | string | — | Main headline, revealed word-by-word. 6–16 words works best. Required. |
| eyebrow | string | — | Small uppercase label above the headline in Electric blue. |
| body | text | — | Optional paragraph that fades in after the headline finishes. |
| floatImages | image[] | — | Up to 4 photos that drift outward from the text center. Desktop only — hidden on mobile. |
| theme | string | "dark" | "dark" or "light". Controls background, text, and image shadow. |
Writing headlines
- 6–16 words is the sweet spot — too short rushes past, too long dilutes the drama
- Strong nouns and verbs. Avoid filler words that illuminate anticlimactically
- The effect is most powerful when the last word lands at an emotional high point
- Works best as a standalone moment between denser sections — give it visual breathing room
Accessibility
Respects prefers-reduced-motion — when set, the headline renders at full opacity immediately, the eyebrow and body are static, and no floating images are rendered. The floating images are marked aria-hidden="true" since they are decorative.
Usage
import { ScrollRevealSection } from '@/components/sections/scroll-reveal-section'
<ScrollRevealSection
block={{
_type: 'scrollRevealSection',
headline: 'Every frame tells a story worth telling twice',
eyebrow: 'The Work',
body: 'Ten years of shooting landscapes, athletes, and people — distilled into one place.',
theme: 'dark',
floatImages: [
{ _type: 'image', asset: { _id: '…', url: '…', metadata: { lqip: '…', dimensions: { … } } } },
// up to 4
],
}}
/>// Via Sanity BlockRenderer — no extra code needed.
// Add to a Page document in Studio:
// Sections → Add item → Scroll Reveal Section