# Lamp Rope: React Native hanging lamp on a Verlet rope physics simulation > Lamp Rope is a React Native and Expo component that hangs a lamp from a > real rope simulation: a chain of point masses solved with time-corrected > Verlet integration and a relaxation constraint pass inside a Reanimated > worklet, drawn as a `@shopify/react-native-skia` path, that you drag with a > pan gesture and yank past a threshold to fire a haptic, a sound and a > flickering light cone. Sold by Motionary as editable TypeScript source. Product page: https://motionary.dev/animations/lamp-rope Price: $10 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: legendary Sold by: Motionary (https://motionary.dev) ## What it is Lamp Rope is a React Native pull-cord toy: a small glowing lamp dangles on a rope from a fixed anchor at the top of the screen, against a deep navy night background. You grab the lamp with your finger and it follows you exactly, with the rope trailing and whipping behind it; let go and it swings back under gravity, overshoots, and settles on a spring toward its rest position while the rope keeps the momentum you gave it. Pull the lamp far enough down (past 100 pixels below rest) and the cord "clicks": Lamp Rope plays a heavy impact haptic, plays a short sound, and fades in a wide light cone that flickers like a real bulb. Pull again and the light goes back off, so the whole thing behaves like a physical pull-chain light switch rather than a canned animation loop. ## What it does - Simulates the rope as a chain of point masses (a segment every 14 pixels between the anchor and the lamp's rest position, so the count follows the canvas height) using time-corrected Verlet integration. - Runs 30 Gauss-Seidel relaxation iterations per frame at `stiffness = 0.6` to hold each segment at its 15 pixel rest length, which is what keeps the rope from stretching when you fling the lamp. - Gives the lamp (the last point) mass `10.0` and damping `0.9` against the rope segments' mass `0.88` and damping `0.95`, so it reads as a weight on a light cord. - Adds a spring force on the lamp only (`k = 1.5`, strength `800`) pulling it back toward the anchor's x and the rest y, plus extra downward force, on top of the global gravity of `{ x: 0, y: 1500 }`. - Pins point 0 as the anchor (`isFixed = true`) and pins the lamp while you drag it, so the solver treats your finger as an immovable constraint. - Converts release velocity into Verlet displacement on `onEnd`, so throwing the lamp actually throws it instead of dropping it dead. - Fires `impactAsync(ImpactFeedbackStyle.Heavy)` from `expo-haptics` and plays `assets/new-song.wav` through `expo-audio` when the pull crosses `PULL_THRESHOLD = 100`, once per drag (guarded by a `hasPlayed` shared value). - Toggles a `themeProgress` shared value between 0 and 1 over 100 ms on each qualifying pull, so the light is a real on/off state, not a one-shot. - Draws the light as a Skia trapezoid cone with a vertical `LinearGradient` and a `BlurMask` of 25, inside a `Group` with `blendMode="luminosity"`. - Re-rolls the bulb flicker every frame once the light is above 0.8: rare deep dips (about 2 percent of frames), occasional mid dips, and constant small jitter otherwise. - Runs on iOS and Android with no custom native code of its own. ## How it works Lamp Rope keeps the entire simulation on the UI thread. `helper/rope.ts` holds the maths: a `Vec2` helper, a `RopePoint` type (`pos`, `oldPos`, `velocity`, `mass`, `damping`, `isFixed`), an `integrate` worklet that advances a point from its previous position under gravity, and a `constrain` worklet that nudges a point and its neighbour back to a fixed segment length weighted by inverse mass. `integrate` is time-corrected Verlet: it scales the carried velocity by `dt / previousFrameDt`, so the rope stays stable when frame intervals drift instead of assuming a fixed step. In `components/Rope.tsx`, the rope points live in a single `useSharedValue`, and a `loop` shared value driven by `withRepeat(withTiming(1, { duration: 16 }), -1, false)` acts as the ticker. The frame's real work happens inside a `useDerivedValue` worklet that reads `loop.value` purely to subscribe to it, then integrates every free point, runs the constraint solver 30 times, writes the lamp position into `lampX` and `lampY` shared values, and rebuilds a `Skia.Path` with `moveTo` and `lineTo`. Because that derived value is passed straight into Skia's ``, no React state changes and nothing crosses to the JavaScript thread, so the rope keeps its frame rate under JS load. Input is a `Gesture.Pan` from `react-native-gesture-handler` whose `onChange` worklet writes `e.x` and `e.y` directly into the last rope point and resets its `oldPos` to the same value (clearing accumulated Verlet velocity so the drag does not fight the solver), while `onEnd` unpins the point and sets `oldPos = pos - velocity * 0.016` to hand the solver your throw. The pull trigger is checked inside the same gesture worklet and calls back to JavaScript with `runOnJS` only for the haptic and `useAudioPlayer` playback. The flicker is a second `useDerivedValue` that also subscribes to `loop` and rolls `Math.random()` per frame to modulate the cone's `opacity`, which is a deliberate choice: it is cheap, it never needs a noise texture, and it is why the bulb reads as unstable rather than as a smooth fade. `cancelAnimation` on the ticker in the effect cleanup stops the whole loop on unmount. ## What you get ``` App.tsx index.ts components/ Rope.tsx helper/ rope.ts assets/ new-song.wav icon.png adaptive-icon.png splash-icon.png light-2.png new-rec.png app.json package.json package-lock.json tsconfig.json README.md ``` - TypeScript source of the `LampRopeSkia` component (`components/Rope.tsx`) - Rope physics helper (`helper/rope.ts`: Verlet `integrate` plus the `constrain` relaxation solver, both worklets you can reuse) - Sound asset for the pull trigger (`assets/new-song.wav`) - Demo `App.tsx` entry - README with install and usage ## Requirements Lamp Rope is pinned in its `package.json` to: - `expo` ^56.0.3 (Expo SDK 56) - `react-native` 0.85.3 - `react` 19.2.3 - `@shopify/react-native-skia` 2.6.2 (rope path, lamp, light cone) - `react-native-reanimated` 4.3.1 (ticker, shared values, physics worklets) - `react-native-worklets` 0.8.3 (required by Reanimated 4) - `react-native-gesture-handler` ~2.31.1 (the pan gesture on the lamp) - `expo-audio` ~56.0.9 (`useAudioPlayer` for the pull sound) - `expo-haptics` ~56.0.3 (heavy impact on the pull) - `expo-asset` ~56.0.13, `expo-status-bar` ~56.0.4 - `react-native-safe-area-context` ~5.7.0 - TypeScript ~6.0.3 (devDependency) Lamp Rope runs in Expo Go on Expo SDK 56. `@shopify/react-native-skia`, the module most people assume forces a custom build, is bundled in Expo Go as of SDK 56, and the rest of what the component imports is Reanimated, Gesture Handler and the Expo modules `expo-audio` and `expo-haptics`, which Expo Go also ships, so `npx expo start` plus the Expo Go app is enough to try Lamp Rope. An Expo development build is still what you would ship to production, and it is what you need as soon as you add a native module that is not in Expo Go or a config plugin of your own: build one with `npx expo run:ios` or `npx expo run:android`. Try the pull on a physical device, because simulators and emulators have no haptic hardware, so the heavy impact on the pull is only felt on a real phone. There is no custom native code in the drop itself, and it works on both iOS and Android. Note that the bundled README text still describes Expo SDK 54 and `expo-av`, while the shipped `components/Rope.tsx` imports `useAudioPlayer` from `expo-audio` and the `package.json` pins Expo SDK 56. ## Install ```bash npx expo install @shopify/react-native-skia react-native-reanimated react-native-worklets react-native-gesture-handler expo-audio expo-haptics ``` ## Usage ```tsx import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { LampRopeSkia } from './components/Rope'; export default function App() { return ( {/* width defaults to the window width, height defaults to 900 */} ); } ``` ## Customization `LampRopeSkia` in Lamp Rope from Motionary takes three props: - `width?: number` (defaults to `useWindowDimensions().width`): canvas width. It also places the anchor and the lamp's rest x at `width / 2` and sizes the light cone at `width * 0.9` at its base. - `height?: number` (default `900`): canvas height. The lamp's rest position is `height * 0.35`, so a shorter canvas gives a shorter rope and fewer simulated points. - `style?: ViewStyle`: merged onto the wrapper `Animated.View` that sits under the gesture detector. Constants at the top of `components/Rope.tsx` tune the rest of the feel: `LAMP_RADIUS` (20), `BACKGROUND_COLOR` (`#0b1026`), `PULL_THRESHOLD` (100 pixels below rest before the light toggles) and `FRAME_DT` (0.016). Inside the physics worklet you can change `gravity` (`{ x: 0, y: 1500 }`), `solverIterations` (30), `stiffness` (0.6) and the segment `resolution` (15) to make the rope heavier, tighter or looser, and the lamp's spring `k` (1.5) and `springStrength` (800) to change how hard it snaps back. Colours are Skia props: the rope is `#e5e7eb` at `strokeWidth={4}` and the lamp is a `#FFC107` circle. ## When to use it - A playful onboarding or welcome screen where pulling a cord literally turns the app's lights on. - An empty state that invites a tap or a tug instead of showing a static illustration. - A settings or theme toggle for dark mode, where the pull-chain metaphor matches what the switch actually does. - A 404, offline or maintenance screen that gives a bored user something physical to fiddle with. - A launch, teaser or countdown screen where the reveal happens when the user pulls the light on. - A reference implementation for any React Native rope, chain, cable, string or cloth simulation: the Verlet integrator and constraint solver in `helper/rope.ts` are generic. ## How to get it - Buy Lamp Rope on its own at https://motionary.dev/animations/lamp-rope for $10 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 make a rope physics animation in React Native?** Lamp Rope from Motionary does it with Verlet integration: store the rope as an array of points with a current and a previous position, advance each one under gravity in a Reanimated worklet, then run a relaxation pass that pulls neighbouring points back to a fixed segment length. Lamp Rope rebuilds the resulting `Skia.Path` in the same `useDerivedValue` worklet, so the whole simulation stays on the UI thread and never re-renders React. **How do I build a pull cord or hanging lamp animation in React Native?** Lamp Rope is that component: a `Gesture.Pan` from `react-native-gesture-handler` pins the last rope point to your finger, and when it passes `PULL_THRESHOLD = 100` pixels below its rest position the component toggles a shared value, fires an `expo-haptics` heavy impact, plays a sound with `expo-audio` and fades in a flickering Skia light cone. **Does Lamp Rope work with Expo Go?** Yes. Lamp Rope runs in Expo Go on Expo SDK 56: `@shopify/react-native-skia` is bundled in Expo Go as of SDK 56, and everything else in the drop is standard Expo SDK 56, Reanimated 4, Gesture Handler, `expo-audio` and `expo-haptics`. An Expo development build (`npx expo run:ios` or `npx expo run:android`) is still what you ship to production, and what you need as soon as you add a native module Expo Go does not bundle or a config plugin; the heavy impact haptic on the pull is only felt on a physical device, not in a simulator. **Does Lamp Rope run on both iOS and Android?** Yes. Lamp Rope from Motionary is built on Expo SDK 56 with `react-native` 0.85.3 and runs on iOS and Android with no custom native code of its own; the haptic uses `expo-haptics` and the sound uses `expo-audio`, both cross platform. **Is the rope simulation fast enough for a production screen?** Yes. In Lamp Rope the integrator, the 30 iteration constraint solver and the Skia path rebuild all run inside one Reanimated worklet on the UI thread, driven by a `withRepeat` ticker, so JavaScript work elsewhere in your app does not stutter the rope, and the rope is drawn as a single Skia stroked path. **Can I change the rope length, the lamp size or the colours?** Yes. Lamp Rope exposes `width`, `height` and `style` as props (rest position is `height * 0.35`, so height sets rope length), and the rest is plain constants in `components/Rope.tsx`: `LAMP_RADIUS`, `BACKGROUND_COLOR`, `PULL_THRESHOLD`, gravity, solver iterations, stiffness and the Skia colours. You get the full editable TypeScript source, so nothing is locked. **How much does Lamp Rope cost and what do I get?** Lamp Rope is $10 USD on Motionary at https://motionary.dev/animations/lamp-rope (price as of 2026-09-01), or it is included in the $79 USD Motionary lifetime pass. You get the full editable TypeScript source: the `LampRopeSkia` component, the reusable Verlet physics helper, the trigger sound asset, a demo `App.tsx` and a README, as an instant download. ## Related drops on Motionary - Apple weather rain ($12): https://motionary.dev/animations/apple-weather-rain the other physics-driven Skia drop, thousands of falling drops with gravity and splash collisions instead of one rope. - Scratch to Reveal ($7): https://motionary.dev/animations/scratch-to-reveal Skia plus a pan gesture as the input, with confetti particles on completion. - Dot Wave Slider ($10): https://motionary.dev/animations/dot-wave-slider another Skia control driven by drag and confirmed with haptics, if you want the pull-and-feel pattern in an actual form control. - Draggable Stamps ($6): https://motionary.dev/animations/draggable-stamps a lighter take on grab-and-throw, pan gestures with momentum and no Skia. - Voice Orb (Free): https://motionary.dev/animations/voice-orb a free Skia plus Reanimated drop, a good place to see the worklet-driven Skia rendering pattern before buying Lamp Rope. ## 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/lamp-rope/llms.txt Describes: https://motionary.dev/animations/lamp-rope Format: llms.txt (https://llmstxt.org) Last updated: 2026-09-01 ```