Skip to content
Native Animation
Text Mask Zoom Scroll animation preview

This preview uses fictional demo content. See the props below for the customization options available in this component.

npx nativeanimation add text-mask-zoom-scroll
Skia

Text Mask Zoom Scroll

A scroll-driven zoom through photo-filled type into destination content, with configurable chapters, mask font asset, text fonts, and surface color.

$9 for this animation

Add to your project

Copy the component and its assets into your app with the Native Animation CLI, or paste the agent prompt into Claude Code, Codex or Cursor once the MCP server is connected.

About this animation

A scroll-driven zoom through photo-filled type into destination content, with configurable chapters, mask font asset, text fonts, and surface color.

How it works

  1. Measure the host with onLayout and measure each chapter's scroll content. Chapter boundaries adapt to wrapped text and galleries.
  2. Build a cached word outline from the font supplied through titleFont. Broad letterforms give the photograph enough surface to read before the reveal.
  3. Magnify the mask, keeping the photograph at viewport size. An aperture inside the chosen A or O counter grows to cover every viewport corner. The full photograph fades in above the paper before the mask fades out, keeping the reveal filled with imagery. zoomScale changes the mask only, so it does not multiply the image resolution requirement.
  4. Hold the photograph while a glass-backed product section scrolls into view. Its description, amenities, local photo gallery, price, and action are ordinary accessible content in the ScrollView.
  5. Derive masks and editorial fades from one continuous progress value. The next chapter starts at its measured boundary. Chapter snapping is optional and only enabled on native platforms.
  6. The demo calls seek() and reset() for autoplay, and shows a sample date-selection summary when a stay is chosen. Direct interaction cancels the scripted scroll.

Install

npx expo install @shopify/react-native-skia expo-blur expo-image expo-linear-gradient react-native-reanimated react-native-worklets

Copy TextMaskZoomScroll.tsx, icons.ts, and index.ts into your project. On web, initialize CanvasKit before mounting with Skia's LoadSkiaWeb or your existing deferred loader. The host supplies and loads interface font families. Supply your own mask font through the required titleFont prop; font files and licenses in demo/assets/ belong to the demo and are not part of the component delivery. Icons use local Lucide paths rendered as SVG by Expo Image, avoiding extra WebGL canvases.

Usage

import TextMaskZoomScroll from './text-mask-zoom-scroll';

<TextMaskZoomScroll
  titleFont={require('./assets/YourDisplayFont.ttf')}
  chapters={[{
    word: 'STAY', image: require('./coastal-home.jpg'), portalIndex: 2,
    accent: '#DCE9AB', heading: 'Stay a little longer.',
    emphasis: 'Leave with more.', description: 'A private cove and coffee on the terrace.',
    stay: 'Coastal House', location: 'COASTAL COLLECTION',
    rating: '4.96', rooms: '3 rooms', access: 'Sea access', price: '$420',
    gallery: [{ source: require('./terrace.jpg'), label: 'Your terrace' }],
    note: '3-night minimum',
  }]}
  onStayPress={(index) => openDatePicker(index)}
/>
const story = useRef<TextMaskZoomScrollRef>(null);

<TextMaskZoomScroll
  ref={story}
  chapters={destinations}
  titleFont={require('./assets/YourDisplayFont.ttf')}
  snapToChapters={false}
  zoomScale={32}
  backgroundColor="#F7F3EE"
  textColor="#243D34"
  mutedColor="#66766B"
  surfaceColor="rgba(20,44,36,0.94)"
  surfaceTextColor="#FAF6EB"
  surfaceMutedColor="#C1CFC4"
  borderColor="#FFFFFF24"
  fontFamily="YourSans"
  titleFontFamily="YourSerif"
  italicFontFamily="YourSerifItalic"
  monoFontFamily="YourMono"
  onChapterChange={trackChapter}
/>
// In a timer or event:
story.current?.seek(0.88, 1400);

The local .ttf in these examples is supplied by your app. Skia loads it through useFont; it does not need to be registered as a React Native font family. Load any interface fonts separately before mounting the component. Keep the license for your chosen font with your app.

Props

PropTypeDefaultPurpose
chaptersreadonly TextMaskChapter[]RequiredWord, image, accent, heading, emphasis, description, stay, rating, rooms, access, price, and portal index. Optional location, badge, note, and gallery with source/label pairs. Use a word with an A or O at portalIndex.
titleFontDataSourceParamRequiredFont source accepted by Skia useFont, such as a local .ttf loaded with require(). Use broad letterforms with an A or O counter for the portal.
fontFamilystringSystemInterface font.
titleFontFamilystringSystemEditorial and product headings.
italicFontFamilystringSystemEditorial emphasis.
monoFontFamilystringSystemLabels and formatted prices.
titlestringPlaces worth\nthe journey.Initial editorial title.
collectionLabelstringA collection of remarkable placesInitial eyebrow.
scrollLabelstringStep insideScroll alternative label.
sectionLabelstringTHE STAYProduct section eyebrow.
priceSuffixstring/ nightPrice unit; prices are already formatted chapter strings.
stayCtaLabelstringView stayEach product button's text and accessible label.
onStayPress(index: number) => void—Selected chapter action; falls back to onCtaPress.
ctaLabelstringExplore staysFooter button text and accessible label.
onCtaPress() => void—Footer action.
footerTitlestringYour next story\nstarts here.Footer heading.
footerDescriptionstringFind a place that feels like you.Footer copy.
onInteraction() => void—Direct touch/pointer interaction notification.
onChapterChange(index: number) => void—Active chapter callback through scheduleOnRN.
zoomScalenumber40Maximum mask magnification, minimum 2.
backgroundColorstring#F7F3EEPaper around the mask.
textColor / mutedColorstring#243D34 / #66766BPaper ink and secondary copy.
surfaceColorstringrgba(20,44,36,0.94)Product and footer material.
surfaceTextColor / surfaceMutedColorstring#FAF6EB / #C1CFC4Product and footer copy.
actionTextColorstring#172721Ink on accent-colored buttons.
borderColorstring#FFFFFF24Material and amenity borders.
snapToChaptersbooleantrueNative-only snapping.
brandReactNode—Noninteractive top-left brand slot.
styleStyleProp<ViewStyle>—Host container styling.
testIDstring—Root ID; controls append -enter-N, -stay-N, -next-N, or -confirm.

The ref exposes seek(progress, durationMs = 2600) and reset(). Integer progress selects a chapter's beginning, and the fractional part advances by one viewport within it: 0.68 reveals the first product sheet and 0.88 brings more of it into view. chapters.length + 0.1 reveals the footer. Product sections can be taller than a viewport; ordinary scroll accesses all content.

Layout and assets

The component fills its host and uses the host's dimensions, including embedded layouts. Product content has a maximum width of 680 points, wraps naturally, and stays scrollable in landscape and short containers. It does not apply safe-area insets: the host screen owns them. Product badges sit below the section label on the left, with 56 points reserved on the right for host controls. The initial scroll hint fades out as soon as scrolling begins and does not repeat in later chapters. Keep the upper-right area clear for host controls when supplying a brand.

Supply local hero images covering the viewport at 3× density. Mask magnification does not enlarge the photograph, so zoomScale does not increase the image resolution requirement. Supply gallery photos at 3× their rendered size.

Compatibility

Expo SDK 58, RN 0.88.0, Reanimated 4.7.1, worklets 0.13.0, Skia 2.14.0. Uses SDK 58 BlurTargetView with Android SDK 31+ blur and its older-device fallback. Supports iOS, Android, and web. Rendering and performance depend on the host device.

Accessibility

Reduced motion removes mask enlargement, scripted travel, and press springs; photographs use a short fade. Product descriptions, gallery labels, prices, and controls are real accessible ScrollView content. The first chapter offers a 48-point scroll button, products offer a 50-point action, and a next-chapter button provides a touch alternative. Typography retains system font scaling and product content grows to fit. Interface text and palette colors are configurable.