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

ElementScroll rangeAnimation
Eyebrow0.01 → 0.10opacity 0 → 1
Images (fan-out)0.04 → 0.52x/y/rotate from center to corner; scale 0.88 → 1
Images (fade-in)0.04 → 0.22opacity 0 → 1 (faster than the drift)
Words (per-word)staggered 0.08 → 0.70opacity 0.12 → 1, overlapping 1.5× slices
Body copy0.64 → 0.80opacity 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.

PropTypeDefaultDescription
headlinestringMain headline, revealed word-by-word. 6–16 words works best. Required.
eyebrowstringSmall uppercase label above the headline in Electric blue.
bodytextOptional paragraph that fades in after the headline finishes.
floatImagesimage[]Up to 4 photos that drift outward from the text center. Desktop only — hidden on mobile.
themestring"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

tsx
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
    ],
  }}
/>
tsx
// Via Sanity BlockRenderer — no extra code needed.
// Add to a Page document in Studio:
// Sections → Add item → Scroll Reveal Section