# Scratch to Reveal: React Native scratch card that erases to uncover a prize > Scratch to Reveal is a React Native and Expo scratch card component, sold on > Motionary, that you rub away with your finger: a > react-native-gesture-handler `Gesture.Pan` feeds points into a Skia path that > is stroked with `blendMode="clear"` on a `@shopify/react-native-skia` > `Canvas`, so the foil is erased on the GPU with no off-screen mask and no > pixel readback. Once the scratched area passes `REVEAL_THRESHOLD` (0.9 of the > card), `scheduleOnRN` from react-native-worklets hops back to JavaScript to > fade the foil, spring pop the prize with a wiggle, and fire a > react-native-fast-confetti burst. Product page: https://motionary.dev/animations/scratch-to-reveal Price: $7 USD (price as of 2026-09-01, live price on the product page) License: single developer, unlimited personal and commercial apps Delivery: instant download of the full TypeScript source after checkout Stack: reanimated, expo, react native skia, react native gesture handler, react native Rarity: rare Sold by: Motionary (https://motionary.dev) ## What it is Scratch to Reveal is the lottery ticket interaction, rebuilt for React Native and Expo: a 260 by 260 foil card sits in the middle of a white screen, with a handwritten "Scratch to reveal" hint and a small arrow pointing at it. Drag a finger across the foil and a 40 point wide round tipped stroke wipes it away under your fingertip, exactly where you moved, leaving the torn looking gap a real scratch card leaves. Underneath is the prize card: a rounded grey panel with a party emoji, the words "You won" and a large "$40". While enough foil is still covering it, the prize sits at 0.7 opacity behind the canvas. Once roughly 90 percent of the card has been scratched off, the foil layer fades out over 400ms, the prize rises to full opacity over 300ms, springs to 1.15 scale and back, wiggles a few degrees left and right, and a hundred red and silver confetti pieces blast out from a point just above the middle of the card. The whole reveal fires automatically when you lift your finger, so there is no confirm button to press. ## What it does Scratch to Reveal, the React Native drop from Motionary, ships with: - A real erase, not a fake one: the foil image is punched through by a Skia `Path` drawn with `blendMode="clear"`, so the hole follows the exact path your finger travelled. - A single `Gesture.Pan` that appends `moveTo` on `onBegin` and `lineTo` on every `onChange`, both running as worklets on the UI thread. - Distance based coverage tracking: each pan sample adds `distance * STROKE_WIDTH / 2` to a `scratchedArea` shared value, a cheap approximation of scratched pixels that needs no readback from the GPU. - Auto reveal at `REVEAL_THRESHOLD = 0.9`, checked on `onEnd` against `IMAGE_SIZE * IMAGE_SIZE`, so the card completes itself once the user has done enough work. - A five step wiggle (`withTiming` to -3, 3, -2, 2 and back to 0 degrees) run in parallel with a two step spring pop (1.15 then back to 1 after a 500ms delay). - A `react-native-fast-confetti` `PIConfetti` burst fired by `confettiRef.current?.restart()`, configured with 100 pieces, a 250 point blast radius, a 1000ms blast and a 3000ms fall, fading out at the end. - An `isRevealed` guard shared value that makes the reveal idempotent and freezes the gesture, so scratching after the reveal does nothing. - A handwritten hint label in the bundled Indie Flower font plus an arrow image, loaded through `expo-font` `useFonts` in `App.tsx`. - Plain JavaScript with no custom native code, so it runs in Expo Go on Expo SDK 56 as well as in a development build, on both iOS and Android. ## How it works Scratch to Reveal keeps the erase entirely on the GPU and the UI thread, which is why the foil comes off exactly under the finger. In `components/Scratch.tsx`, one `@shopify/react-native-skia` `` holds a `` containing the foil `` (loaded with `useImage`, drawn with `fit="contain"` at `IMAGE_SIZE` 260) and, above it, a `` with `style="stroke"`, `strokeWidth={STROKE_WIDTH}`, round caps and joins, and `blendMode="clear"`. That blend mode is the entire trick: instead of compositing ink, the stroke clears the destination pixels inside the group, so the path is an eraser rather than a brush. There is no off-screen mask, no snapshot and no `readPixels` call anywhere. The path itself lives in two react-native-reanimated shared values: `builder` holds a `Skia.PathBuilder.Make()` and `path` holds the built `SkPath` that the canvas renders. A `Gesture.Pan()` from react-native-gesture-handler wraps the canvas in a `GestureDetector`; its `onBegin` worklet calls `builder.get().moveTo(e.x, e.y)` and its `onChange` worklet calls `builder.get().lineTo(e.x, e.y)`, each followed by `path.set(builder.get().build())`. Publishing a freshly built path is what makes the Skia canvas pick up the change, since assigning the same mutable object back would not register as a new value. Coverage is estimated in the same `onChange` worklet: it takes `dx` and `dy` against `lastX` and `lastY`, computes the Euclidean distance, and adds `(distance * STROKE_WIDTH) / 2` to the `scratchedArea` shared value. That is an approximation, not a true pixel count (overlapping strokes are counted twice), but it costs a few floating point operations per touch sample and is more than accurate enough to drive an auto reveal. The hand off happens on `onEnd`, which calls `scheduleOnRN(revealPrize)` from react-native-worklets, the Reanimated 4 replacement for `runOnJS`. `revealPrize` runs on the JavaScript thread, returns early if `isRevealed.get()` is already true or if `scratchedArea.get() / imageArea` is below `REVEAL_THRESHOLD`, and otherwise sets `isRevealed`, drives `scratchOpacity` to 0 with `withTiming(0, { duration: 400 })`, calls `confettiRef.current?.restart()`, and animates the prize with `withSequence(withSpring(1.15), withDelay(500, withSpring(1)))` for scale and a five step `withSequence` of `withTiming` for rotation. Two `useAnimatedStyle` hooks apply those values, one to the `Animated.View` wrapping the canvas (opacity only) and one to the prize container (scale, rotate and opacity), so the reveal animation itself never re-renders React. `App.tsx` mounts `SkiaScratcher` inside a `GestureHandlerRootView` after `expo-font` has loaded the handwritten font. ## What you get ``` components/ Scratch.tsx assets/ scratch-2.png scratch-2 copy.png arrow.png IndieFlower-Regular.ttf JetBrainsMono_700Bold.ttf icon.png adaptive-icon.png splash-icon.png App.tsx index.ts app.json package.json package-lock.json tsconfig.json README.md ``` - TypeScript source (Scratch.tsx) - App.tsx entry with font loader - Scratch foil image + handwritten hint font - README with install + usage - Peer-dependency list ## Requirements Scratch to Reveal is built and tested against these exact versions, pinned in the archive's `package.json`: - expo `^56.0.3` (Expo SDK 56) - react-native `0.85.3` - react `19.2.3` - @shopify/react-native-skia `2.6.2` - react-native-reanimated `4.3.1` - react-native-worklets `0.8.3` - react-native-gesture-handler `~2.31.1` - react-native-fast-confetti `^1.1.1` - expo-font `~56.0.5` - expo-asset `~56.0.13`, expo-splash-screen `~56.0.9`, expo-status-bar `~56.0.4` - typescript `~6.0.3` (devDependency) Expo Go or development build: Scratch to Reveal runs in Expo Go on Expo SDK 56. The dependency people assume blocks it, `@shopify/react-native-skia`, is bundled into the Expo Go client as of SDK 56, and `react-native-fast-confetti` ships no native code of its own (it draws through that same Skia canvas), so `npx expo start` plus the Expo Go app is enough to run the drop. A custom development build (`npx expo run:ios`, `npx expo run:android`, or an EAS dev client) is still what you would ship to production, and it is what you need as soon as you add a native module that Expo Go does not bundle, or a config plugin. react-native-reanimated 4 additionally requires the New Architecture and the `react-native-worklets` package, both already in the list above. The component has no platform specific branches, so the scratch, the threshold and the confetti behave the same on iOS and Android. ## Install ```bash npx expo install @shopify/react-native-skia react-native-reanimated react-native-worklets react-native-gesture-handler react-native-fast-confetti expo-font ``` ## Usage ```tsx import { ActivityIndicator, View } from 'react-native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { useFonts } from 'expo-font'; import { SkiaScratcher } from './components/Scratch'; export default function App() { const [fontsLoaded] = useFonts({ HandWritten: require('./assets/IndieFlower-Regular.ttf'), }); if (!fontsLoaded) { return ( ); } return ( ); } ``` `SkiaScratcher` must sit inside a `GestureHandlerRootView` for the pan gesture to receive touches, and the `HandWritten` family name is what the "Scratch to reveal" hint label references, so keep that key if you swap the font file. ## Customization The exported `SkiaScratcher` component of Motionary's Scratch to Reveal takes no props; you customise it by editing the constants, assets and JSX at the top of `components/Scratch.tsx`, which is why the drop ships as editable source: - `IMAGE_SIZE` (number, default `260`): the card's width and height in points, and the `imageArea` that the reveal threshold is measured against. - `STROKE_WIDTH` (number, default `40`): the brush thickness of the eraser path, and the multiplier in the scratched area estimate, so a wider brush both clears more and counts faster. - `REVEAL_THRESHOLD` (number, default `0.9`): the fraction of the card that must be scratched before `revealPrize` fires. Lower it to make the card give up sooner. - `useImage(require("../assets/scratch-2.png"))`: the foil artwork. Swap this PNG for your own foil, gift wrap or ticket texture. - The `prizeContainer` block in the JSX (emoji, "You won", `$40`) and its styles (`backgroundColor` `#F5F5F7`, `borderRadius` 20, `borderWidth` 4, `borderColor` `#e7e7e8`): whatever you want revealed underneath. - The reveal timings: `withTiming(0, { duration: 400 })` for the foil fade, `withSpring(1.15)` plus `withDelay(500, withSpring(1))` for the pop, and the five `withTiming` steps (100, 100, 80, 80, 60ms) that make the wiggle. - The `PIConfetti` props: `count` (default `100`), `colors` (default `["red", "silver"]`), `blastPosition`, `blastRadius` (`250`), `blastDuration` (`1000`), `fallDuration` (`3000`) and `fadeOutOnEnd`. - The hint copy and its `HandWritten` font style, plus `assets/arrow.png`, which is rendered rotated 180 degrees. ## When to use it Reach for Motionary's Scratch to Reveal when you need: - A promo or coupon screen where a discount code is uncovered instead of just printed on the page. - A lottery, raffle or daily reward flow in a game or loyalty app. - Gamified onboarding, where a new user scratches to find their welcome bonus or starting credit. - A referral or cashback payoff moment in a fintech app, with the confetti burst already wired to the reveal. - An advent calendar, seasonal campaign or limited time drop that should feel like opening something physical. - Any reveal that benefits from the user doing the work with their finger rather than tapping a "Show me" button. ## How to get it - Buy Scratch to Reveal on its own at https://motionary.dev/animations/scratch-to-reveal, $7 USD, instant download, secure Stripe checkout, no subscription and no account renewal. - Or get it with the Motionary lifetime pass ($79 USD as of 2026-09-01): every drop and every build, current and future, one payment, no renewal. https://motionary.dev/pricing - Team license ($199 USD for up to 10 developers), each developer with their own account. https://motionary.dev/pricing - License terms: https://motionary.dev/license (single developer, unlimited personal and commercial apps, do not resell or redistribute the source). - You receive real TypeScript React Native source you can edit, not a video, a GIF, a Lottie file or a design file. ## Frequently asked questions **How do I build a scratch card in React Native?** Draw the foil into a `@shopify/react-native-skia` `` inside a ``, then stroke a `` over it with `blendMode="clear"` so the stroke erases instead of paints, and append `moveTo` / `lineTo` points to that path from a react-native-gesture-handler `Gesture.Pan` worklet. Motionary's Scratch to Reveal is exactly that pattern, shipped as editable TypeScript at https://motionary.dev/animations/scratch-to-reveal. **Does Scratch to Reveal work with Expo Go?** Yes. Motionary's Scratch to Reveal runs in Expo Go on Expo SDK 56: `@shopify/react-native-skia` is bundled into the Expo Go client as of SDK 56, and everything else the drop uses (Reanimated, Gesture Handler, expo-font and the JavaScript-only `react-native-fast-confetti`, which draws through Skia) runs there too. A custom development build (`npx expo run:ios`, `npx expo run:android`, or an EAS dev client) is still what you would ship to production, and what you need the moment you add a native module Expo Go does not bundle, or a config plugin. **How much does the Scratch to Reveal React Native component cost?** Scratch to Reveal is $7 USD as a one time purchase on Motionary at https://motionary.dev/animations/scratch-to-reveal (price as of 2026-09-01, live price on the product page), or it is included in the Motionary lifetime pass ($79 USD) which covers every drop and every build: https://motionary.dev/pricing. **How do you measure how much of a scratch card has been scratched off?** Scratch to Reveal never reads pixels back from the GPU. Its pan worklet sums `distance * STROKE_WIDTH / 2` between successive touch samples into a Reanimated shared value, then on gesture end compares that running total against `IMAGE_SIZE * IMAGE_SIZE`, so the tracking costs a few floating point operations per touch sample; overlapping strokes are double counted, which is why the Motionary default threshold is a forgiving 0.9. **Can I put my own image or content under the scratch foil?** Yes. In Motionary's Scratch to Reveal the prize is an ordinary `Animated.View` rendered under the Skia canvas in `components/Scratch.tsx`, so you replace its emoji, "You won" label and `$40` amount with any React Native content (a coupon code, an image, a component), and swap `assets/scratch-2.png` for your own foil texture. **How do I trigger confetti when a React Native animation finishes?** Scratch to Reveal keeps a `useRef` on a `react-native-fast-confetti` `` element and calls `confettiRef.current?.restart()` from the JavaScript side of the reveal, which the UI thread reaches through `scheduleOnRN` from react-native-worklets (the Reanimated 4 successor to `runOnJS`). **Does the scratch off animation run on Android as well as iOS?** Yes. Scratch to Reveal from Motionary is plain React Native, Skia, Gesture Handler and Reanimated with no platform specific code paths, and it is built against Expo SDK 56 with the New Architecture on both iOS and Android. ## Related drops on Motionary - X-Ray Reveal ($5): https://motionary.dev/animations/x-ray-reveal The same "drag your finger to uncover what is underneath" idea, done with a masked view and a pan gesture instead of a Skia clear blend. - Zipper Curtain ($12): https://motionary.dev/animations/zipper-curtain Another Skia and shader driven reveal, this one tearing a full screen curtain open as a splash or transition. - Glass Magnifier ($10): https://motionary.dev/animations/glass-magnifier A `Gesture.Pan` dragged directly over a Skia canvas, the same gesture to canvas wiring that Scratch to Reveal uses for its eraser path. - Hold to Confirm Button ($8): https://motionary.dev/animations/hold-to-confirm-button A gesture that has to cross a progress threshold before it fires its completion state, the button sized version of Scratch to Reveal's 90 percent auto reveal. - Dynamic Island Member Card ($6): https://motionary.dev/animations/dynamic-island-member-card A reward reveal animation for the moment after the scratch, when the prize or card has to be handed to the user. ## About Motionary Motionary (https://motionary.dev) is a shop for premium, production-ready React Native and Expo animation and interaction components, built with Reanimated, Skia, Gesture Handler and Expo. Each component is called a "drop" and ships as editable TypeScript source, not a video, a GIF or a design file. Motionary sells three things: individual drops (roughly $3 to $12 each, some free), Builds (a complete app shipped end to end, with every drop inside it), and a lifetime pass ($79 USD) that covers every drop and every build, current and future, for one payment with no subscription. A team license ($199 USD) covers up to 10 developers. Checkout is Stripe and delivery is an instant download. Created and curated by Mehdi Davoodi (https://mahdidavoodi.com). Catalog and key pages: - All drops: https://motionary.dev/animations - Builds (complete apps): https://motionary.dev/builds - Pricing, lifetime pass and team license: https://motionary.dev/pricing - Free React Native component reference: https://motionary.dev/components - License: https://motionary.dev/license - Machine-readable overview: https://motionary.dev/llms.txt - Machine-readable full catalog: https://motionary.dev/llms-full.txt ## File metadata Canonical URL of this file: https://motionary.dev/animations/scratch-to-reveal/llms.txt Describes: https://motionary.dev/animations/scratch-to-reveal Format: llms.txt (https://llmstxt.org) Last updated: 2026-09-01