Notion-Style QR Code Animation
Saturday, December 27, 2025
- skia
- reanimated
demosnotion-qrcode
An ongoing collection of React Native animations crafted with Reanimated, Gesture Handler, and Skia.
TypeScript3.2k297
A 3D animation where avatar images arranged in a rotating torus morph into a scannable QR code. This demo showcases Skia rendering, optimal point matching with the Hungarian algorithm, and wave-based staggered transitions.
What's This About?
This animation features:
- Avatar images float in a 3D rotating torus (donut shape)
- On toggle, avatars smoothly morph into a functional QR code
- A wave effect sweeps around the torus during the transition
- Corner radius animates from circular (torus) to square (QR)
- Haptic feedback provides tactile response during morphing
How It Works
The animation pipeline has three main stages:
Shape Generation (useShapeData hook):
- Generates N points distributed on a 3D torus surface using Fibonacci distribution
- Generates N points for the QR code (one per black module)
- Uses the Hungarian algorithm to find optimal 1:1 mapping between torus and QR points, minimizing total travel distance for smooth morphing
Animation Loop (createPicture worklet):
- Runs every frame on the UI thread via Skia
- Interpolates each point's position between torus → QR based on progress
- Applies 3D rotation with perspective projection
- Depth-sorts all points for correct occlusion (painter's algorithm)
Wave Effect:
- Points don't animate simultaneously - they have staggered delays
- Delay is based on angular position around the torus
- Creates a "wave" that sweeps around during morphing
Technical Highlights
- React Native Skia: Picture-based rendering for 60fps performance
- Reanimated Worklets: All animation logic runs on the native UI thread
- Hungarian Algorithm: O(n³) optimal assignment for smooth point transitions
- Perspective Projection: True 3D depth with objects scaling based on distance
- Haptic Feedback: Front-loaded burst of haptics at animation start
Component API
The component is fully configurable:
import { QRCodeAnimation, QRCodeAnimationRef } from './notion-qrcode';
const ref = useRef<QRCodeAnimationRef>(null);const progress = useSharedValue(0);
<QRCodeAnimation ref={ref} qrData="https://your-url.com" sprite={{ source: require('./avatars.png'), cols: 5, rows: 4, cellSize: 128, numAvatars: 20, }} colors={{ hue: 220, saturationRange: [30, 70], lightnessRange: [70, 85], }} progress={progress}/>;
// Toggle between torus and QR coderef.current?.toggle();Props
- qrData (string) - URL or text to encode in the QR code
- sprite (SpriteConfig) - Avatar sprite sheet configuration
- colors (ColorConfig) - HSL color palette for backgrounds
- torus (TorusConfig) - Torus dimensions (major/minor radius)
- progress (SharedValue<number>) - External progress value for button sync
- ref (RefObject) - Exposes toggle() method
Unlock the beauty of React Native animations
Learn Reanimated, Gesture Handler & Skia with Reanimate.dev
Feedback on Notion Qrcode
Ideas, bugs, or anything that felt off: I read every message. For anything else, write to hello@reactiive.io.