About this animation
A product hero that draws construction lines before revealing artwork, with configurable image, copy, colors, and fonts.
Why this one
A static product photo becomes a small act of discovery: axes grow from the object's center, four continuous circles draw in sequence, and a circular clip reveals the sculpture. A luminance-masked light pass travels through the chrome itself. The staggered headline and paper-colored journal section give the cover an independent editorial identity.
How it works
onLayoutmeasures the container. Text stays in flow and short containers scroll instead of clipping.- One construction shared value drives center-out axes, staggered path trimming, intersection dots, the circular image clip, and headline entries. The image waits to load before the timeline starts.
- A Skia image shader screens accent light into the bright pixels of the artwork. No opaque overlay simulates the sheen.
- Native scroll updates a continuous shared value for counter-rotation, 18-point parallax, and up to 4% zoom. The paper section physically sweeps into view; the fixed wordmark and menu ink invert with it.
- Navigation uses measured section offsets and an interruptible spring. Physical touch cancels automation. Buttons use Gesture Handler taps with a separate pressure spring.
- The demo reveals the cover, holds for two seconds, scrolls to the journal at 4 seconds, returns at 6.8 seconds, and replays at 9 seconds.
Install
npx expo install @shopify/react-native-skia expo-image react-native-gesture-handler react-native-reanimated react-native-workletsUse a GestureHandlerRootView at your app root. Configure Reanimated and Worklets for your Expo SDK. On web, load CanvasKit before mounting this component (for example, a Skia-aware lazy boundary). The component has no imports from the catalog or shared theme. Icons: Lucide (ISC); copied paths are in icons.ts.
Usage
import { ConstructionLinesHero } from './construction-lines-hero';
<ConstructionLinesHero
image={require('./sculpture.jpg')}
headline={[
{ text: 'SHAPED BY', indent: 0 },
{ text: 'CURIOSITY.', indent: 0.18 },
]}
body="Objects made for a closer look."
sections={[
{
headline: [{ text: 'THE JOURNAL', indent: 0.05 }],
label: '[journal]',
body: 'Notes from the studio.',
image: require('./detail.jpg'),
imageLabel: 'Polished sculpture detail',
},
]}
/>const hero = useRef<ConstructionLinesHeroRef>(null);
<ConstructionLinesHero
ref={hero}
image={artwork}
headline={lines}
body={copy}
sections={journal}
fontFamily="YourInterfaceFont"
monoFontFamily="YourMonoFont"
duration={1600}
sheenIntensity={0.35}
colors={{ accent: '#82D9FF' }}
onSectionChange={index => trackSection(index)}
/>
// From event handlers:
hero.current?.replay();
hero.current?.scrollToSection(1);Props
| Prop | Type | Default | Purpose |
|---|---|---|---|
image | Skia DataSourceParam | Required | Sculpture artwork |
imageLabel | string | Sculptural object | Image description |
headline | HeadlineLine[] | Required | Text and fractional indents, clamped to 0–0.35 |
body | string | Required | Supporting copy |
sections | EditorialSection[] | Required | Headlines, labels, copy, and Expo image sources |
brand | string | Studio | Text wordmark |
fontFamily / monoFontFamily | string | System | Interface and technical typography |
colors | Partial<ConstructionLinesHeroColors> | See below | Complete dual-surface theme |
duration | number | 1800 | Intro milliseconds; minimum 600 |
headlineSize | number | 30 | Maximum headline size; compact layouts cap at 27 |
sheenIntensity | number | 0.55 | Luminance-masked accent light strength |
nextLabel / backLabel | string | Explore / Back to top | Touch alternatives to scrolling |
menuLabel | string | Go to editorial section | Navigation accessibility label |
replayLabel | string | Replay construction | Replay accessibility label |
onInteraction | () => void | — | Physical input; demo stops autoplay |
onSectionChange | (index: number) => void | — | Section notification; hero is index 0 |
onReveal | () => void | — | Construction completion |
style | StyleProp<ViewStyle> | — | Root style |
testID | string | construction-lines-hero | Root; suffixes -menu, -replay, -next, -back-N |
Palette defaults: background: #030405, foreground: #F6F6F3, muted: #A3A5A7, sectionBackground: #F5F4F0, sectionForeground: #141719, sectionMuted: #62666B, accent: #BFFF62. Override all seven for a complete custom theme. The editorial dark-to-paper transition is intentional and independent of system appearance.
The ref exposes replay() and scrollToSection(index). Index 0 is the hero; section indexes start at 1 and are clamped to the supplied list.
Layout and assets
This is a full-page, vertically scrolling component. The parent provides safe-area insets and a bounded height. It also fits embedded containers; below 620 points tall the cover scrolls. Text scales normally and may wrap with larger accessibility fonts. Content width caps at 540 points on tablets. Artwork caps at 242 points; the supplied 768×1344 JPEG covers 3× pixel density including the 4% zoom. The top-right 56-point strip is reserved for a host close button; navigation ends 56 points from the right edge.
Compatibility
Built for Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Worklets 0.13.0, Gesture Handler 3.3, and Skia 2.14.0. Uses the installed Expo Go modules; no extra native dependency was added. Supports iOS, Android, and web.
Accessibility
All controls have button roles, configurable labels, 48-point targets, and accessibility activation handlers. Artwork has image descriptions and headlines have header roles. Major section changes announce the destination heading. Reduced motion uses a 200 ms construction fade, disables path drawing, sheen, counter-rotation, parallax, and zoom, and replaces automated scrolling with a destination fade. Native manual scroll remains available. Replay and section buttons provide tap alternatives. The demo stops its automatic sequence on touch or drag.




