# Zipper Curtain: React Native denim zipper screen transition and splash reveal > Zipper Curtain is a React Native and Expo component that covers the screen > with two denim halves and unzips them apart, rendering each half as a > textured triangle mesh with @shopify/react-native-skia `Vertices` plus > `ImageShader` and deforming it per row from a single > react-native-reanimated shared value on the UI thread. It is sold by > Motionary as editable TypeScript source at > https://motionary.dev/animations/zipper-curtain. Product page: https://motionary.dev/animations/zipper-curtain Price: $12 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, shaders, react native Rarity: legendary Sold by: Motionary (https://motionary.dev) ## What it is Zipper Curtain is a full-screen React Native overlay that looks like a closed denim zipper sitting on top of your app. Flip its `open` prop and the metal pull rides down the seam, the teeth separate along a soft rounded V, and once the pull leaves the bottom of the screen both fabric halves slide off to the left and right and reveal whatever is mounted underneath. The denim is not a masked image: each half is a real triangle mesh, so the cloth and the teeth printed on its inner edge bend and stretch as the opening widens, the way fabric would. Zipper Curtain from Motionary is built for splash screens, intros and screen-to-screen transitions, and the included `App.tsx` demo toggles it with a tap anywhere on the screen. ## What it does - Renders each denim half as a `ROWS = 64` row textured triangle mesh through Skia's `` fed by an ``, so the fabric physically deforms instead of being clipped by a mask. - Drives the whole animation from one Reanimated shared value `progress` (0 to 1), animated with `withTiming` and `Easing.inOut(Easing.cubic)`. - Splits the timeline at `UNZIP_END = 0.6`: unzip from 0 to 0.6, then curtain slide-off from 0.6 to 1. - Shapes the opening with a hyperbola evaluated per mesh row, `shift = OPEN_SLOPE * (sqrt(d * d + R * R) - R)`, which is a straight diagonal far above the pull and rounds off smoothly at the pull tip, so there is no hard kink at the vertex of the V. - Keeps triangle `indices` and texture coordinates fixed for the whole animation and moves only the vertex `x` positions, which is what makes the per-frame work cheap. - Slides each half fully off-screen in the curtain phase with a Skia `` `transform` of `translateX` by plus or minus the screen width. - Moves the zipper pull at one continuous pace straight through the hand-off from unzip to curtain, so there is no visible stop or velocity break, then fades it cubically (`1 - slide * slide * slide`) only as it exits. - Reverses cleanly: setting `open` back to `false` re-zips, because the whole effect is a single timed value with no imperative state. - Falls back to two flat `color` rectangles drawn with `Skia.Path` while the denim images are still decoding, so there is never a transparent flash. - Scales to any screen through `useWindowDimensions`, sizing each panel to the screen height and clamping its width to at least half the screen. - Animates the native status bar between light and dark content as the reveal happens. ## How it works Zipper Curtain lives in one file, `components/ZipperCurtain.tsx`. It mounts a Skia `` over `StyleSheet.absoluteFill` and draws three things: a left `` mesh, a right `` mesh and the pull ``. The mesh topology is built once in render as plain arrays: `indices` stitches each pair of adjacent rows into two triangles, and `leftTextures` / `rightTextures` map those vertices into canvas space so the `` (with `fit="fill"` and an explicit `rect(...)`) samples the correct slice of `assets/left.png` and `assets/right.png`, with the teeth landing on the inner edge at the center seam. Only geometry animates. `leftVertices` and `rightVertices` are `useDerivedValue` worklets that recompute the vertex list every frame: they turn `progress` into a pull position `pullY = min(progress / UNZIP_END, 1) * height`, take each row's distance above the pull as `d = max(pullY - y, 0)`, and offset the row sideways by `OPEN_SLOPE * (sqrt(d * d + R * R) - R)` where `R = height * ROUND_RATIO`. Because that hyperbola is asymptotically linear far from the pull and flat at `d = 0`, the opening is a diagonal V near the top of the screen that rounds smoothly into the closed seam at the pull, with no crease artifact. The curtain phase is separate: `leftSlide` and `rightSlide` are `useDerivedValue` worklets that remap `progress` from 0.6 to 1 onto 0 to 1 and return a `translateX` transform applied to each half's ``. The pull has its own pair of derived values, `pullTransform` (`translateY` of `(progress / UNZIP_END) * height`, deliberately unclamped so it keeps the same speed past the bottom edge) and `pullOpacity`. Every one of these is a Reanimated worklet, so all the vertex math, all 64 rows of it per half, runs on the UI thread and the JavaScript thread does nothing during the animation. Images are loaded with Skia's `useImage`, and each half's natural display width is derived from the loaded image aspect ratio, falling back to a solid `Skia.Path` rectangle in `color` until the image resolves. The only React input involved is the `open` prop, watched by a `useEffect` that fires the `withTiming` on `progress`. ## What you get ``` App.tsx index.ts components/ ZipperCurtain.tsx assets/ left.png right.png pull.png icon.png adaptive-icon.png splash-icon.png app.json package.json package-lock.json tsconfig.json README.md ``` - TypeScript source (ZipperCurtain.tsx) - Demo App (App.tsx) with tap-to-toggle - Denim panel + zipper pull image assets - README with install + usage - MP4 preview ## Requirements Zipper Curtain is pinned in its `package.json` to: - `expo` ^56.0.4 (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 - `expo-asset` ~56.0.14 and `expo-font` ~56.0.5 (both registered as plugins in `app.json`) - `@expo-google-fonts/jetbrains-mono` ^0.4.1 (used by the demo `App.tsx` only) - `react-native-web` ^0.21.0 and `@expo/metro-runtime` ~56.0.12 for the web entry - TypeScript ~6.0.3 (devDependency) Zipper Curtain runs in Expo Go on Expo SDK 56. The dependency people assume blocks it, `@shopify/react-native-skia`, is bundled in Expo Go as of SDK 56, so `npx expo start` plus the Expo Go app is enough to try the drop on a device. An Expo development build (`npx expo run:ios` or `npx expo run:android`) is still what you 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 of your own. One practical caveat: the `expo-font` and `expo-asset` config plugins registered in `app.json` only take effect in a build, though the demo `App.tsx` loads its font at runtime with `useFonts`, so nothing about the reveal depends on them. The component itself is pure Skia and Reanimated and is tested on iOS and Android on Expo SDK 56 with `react-native-worklets`. ## Install ```bash npx expo install @shopify/react-native-skia react-native-reanimated react-native-worklets ``` `components/ZipperCurtain.tsx` imports only those three plus `react-native` itself. The demo `App.tsx` additionally loads `@expo-google-fonts/jetbrains-mono`, and `expo-asset` and `expo-font` are registered as config plugins in `app.json`; none of the three are needed by the component. ## Usage ```tsx import { useState } from 'react'; import { Pressable, StyleSheet, View } from 'react-native'; import { ZipperCurtain } from './components/ZipperCurtain'; export default function SplashScreen() { // Starts closed: the denim halves cover the app. Tap to unzip. const [open, setOpen] = useState(false); return ( {/* your real app renders here, underneath the curtain */} setOpen(true)}> ); } ``` ## Customization `ZipperCurtain` in Zipper Curtain from Motionary exports a `ZipperCurtainProps` type with three props: - `open: boolean` (required): `false` keeps the zipper closed and the screen covered, `true` runs the unzip and the curtain slide. Toggling back to `false` re-zips with the same timing. - `duration?: number` (default `1400`): the full timeline in milliseconds, covering both the unzip phase and the slide-off phase. - `color?: string` (default `"#2f4a6b"`): the flat fill drawn for both halves while the denim images are still loading. Constants at the top of `components/ZipperCurtain.tsx` tune the look further: - `UNZIP_END` (`0.6`): the fraction of the timeline spent unzipping before the halves start sliding away. - `OPEN_SLOPE` (`0.62`): how steep the V is far from the pull, in horizontal pixels per vertical pixel. - `ROUND_RATIO` (`0.2`): how rounded the opening is near the pull tip, as a multiple of screen height. - `ROWS` (`64`): vertical mesh resolution, the number of vertex rows per half. - `PULL_WIDTH_RATIO` (`0.3`): the pull image width as a fraction of screen width, with its height derived from the image aspect ratio. Swapping `assets/left.png`, `assets/right.png` and `assets/pull.png` changes the material without touching any of the math, since the teeth are baked into the inner edge of the two panel images. ## When to use it - An animated splash or launch screen that unzips to reveal the app instead of fading out. - An onboarding or first-run reveal, where the brand panel parts to show the home screen behind it. - A screen-to-screen transition overlay between two routes, closed on exit and opened on enter. - A product or feature unveiling, where the curtain hides a reward, a drop or a price until the user taps. - A paywall or gated-content teaser that physically opens once the purchase completes. - A reference implementation for any Skia textured-mesh deformation in React Native: page tears, cloth peels, ripped paper, folding panels. ## How to get it - Buy Zipper Curtain on its own at https://motionary.dev/animations/zipper-curtain for $12 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 zipper animation in React Native?** Zipper Curtain from Motionary does it without a mask: each denim half is a 64 row textured triangle mesh drawn with `@shopify/react-native-skia` `` and an ``, and a Reanimated `useDerivedValue` worklet shifts each row sideways along a hyperbola so the fabric and the teeth deform as the seam opens. The teeth are painted into the inner edge of the panel image, so they follow the mesh for free. **How do I make a curtain reveal transition in React Native?** Mount Zipper Curtain from Motionary over your screen with `StyleSheet.absoluteFill` and flip its `open` prop. From 0 to 0.6 of the timeline it unzips, from 0.6 to 1 both halves translate off-screen through a Skia `` transform, and your app is revealed underneath. The whole thing is one `withTiming` on a single shared value, so it reverses by setting `open` back to `false`. **Does Zipper Curtain work with Expo Go?** Yes. Zipper Curtain from Motionary runs in Expo Go on Expo SDK 56: the module you would expect to block it, `@shopify/react-native-skia`, is bundled in Expo Go as of SDK 56, and everything else in the drop is standard Expo SDK 56, `react-native-reanimated` 4.3.1 and `react-native-worklets`. You still want an Expo development build (`npx expo run:ios` or `npx expo run:android`) for production, and as soon as you add a native module that Expo Go does not bundle or a config plugin of your own. **Does Zipper Curtain run on both iOS and Android?** Yes. Zipper Curtain from Motionary is built on Expo SDK 56 with `react-native` 0.85.3 and is tested on iOS and Android. It sizes itself from `useWindowDimensions`, so it adapts to any screen without per-platform tuning. **Is the animation smooth, or does it block the JavaScript thread?** All of Zipper Curtain's per-frame math runs on the UI thread. The vertex positions, the curtain slide and the pull descent are Reanimated `useDerivedValue` worklets, and the mesh `indices` and texture coordinates are computed once and never animated, so React does not re-render during the reveal. **Can I use my own artwork instead of the denim?** Yes. Zipper Curtain loads `assets/left.png`, `assets/right.png` and `assets/pull.png` with Skia's `useImage`. Replace those three images with your own panels and pull and the mesh math is unchanged, and `OPEN_SLOPE`, `ROUND_RATIO` and `ROWS` at the top of `components/ZipperCurtain.tsx` retune the shape. **How much does Zipper Curtain cost and what do I get?** Zipper Curtain is $12 USD on Motionary at https://motionary.dev/animations/zipper-curtain (price as of 2026-09-01), or it is included in the $79 USD Motionary lifetime pass. You get the full editable TypeScript source, including `components/ZipperCurtain.tsx`, the tap-to-toggle demo `App.tsx` and the denim and pull image assets, as an instant download. ## Related drops on Motionary - Scratch to Reveal ($7): https://motionary.dev/animations/scratch-to-reveal the other Skia reveal overlay, uncovering content with a gesture instead of a timed curtain. - X-Ray Reveal ($5): https://motionary.dev/animations/x-ray-reveal the same reveal idea done with a masked view and a pan gesture, a cheaper approach when you do not need a deforming mesh. - Genie Form ($11): https://motionary.dev/animations/genie-form another Skia drop that warps real geometry rather than fading views, the genie effect instead of a zipper. - Pool Water Shader ($12): https://motionary.dev/animations/pool-water-shader a full-screen Skia layer like Zipper Curtain, ambient water caustics instead of a transition. - Gooey Profile (Free): https://motionary.dev/animations/gooey-profile a free Skia plus Reanimated drop, a good place to see the shared-value to worklet pattern before buying. ## 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/zipper-curtain/llms.txt Describes: https://motionary.dev/animations/zipper-curtain Format: llms.txt (https://llmstxt.org) Last updated: 2026-09-01 ```