reanimated-pause-state
v0.1.1
Published
Pause Reanimated animations and inspect their state at any frame
Maintainers
Readme
reanimated-pause-state
Pause Reanimated animations and capture state snapshots for debugging and design review.
What it does
- Pause/resume animations created with Reanimated factory functions
- Capture animation state snapshots with current values, timing, and source locations
- Filter snapshots by component name, file, or line proximity
- Generate prompt-ready markdown for LLM-assisted debugging
Zero overhead in production - all functionality is gated behind __DEV__.
Getting Started
1. Install
npm install reanimated-pause-state2. Add Babel plugin
// babel.config.js
module.exports = {
presets: ['babel-preset-expo'],
plugins: [
'reanimated-pause-state/babel', // MUST be first
'react-native-reanimated/plugin', // MUST be last
],
};3. Clear cache and restart
npx expo start --clear4. Add pause control to your app
import { pause, resume, getSnapshotMarkdown } from 'reanimated-pause-state';
import * as Clipboard from 'expo-clipboard';
// In a dev menu, button, or shake handler:
const handlePause = () => {
pause();
const markdown = getSnapshotMarkdown();
Clipboard.setStringAsync(markdown);
Alert.alert('Copied', 'Animation state copied to clipboard');
};Now paste into Claude or your team chat with full animation context.
Usage
import {
pause,
resume,
toggle,
snapshotNow,
getSnapshotMarkdown,
isInstalled,
isPaused
} from 'reanimated-pause-state';
// Verify setup
console.log('Plugin installed:', isInstalled());
// Pause animations
pause();
// Take a snapshot of current animation state
const snapshot = snapshotNow();
console.log(snapshot);
// {
// timestamp: 1706123456789,
// animations: [
// {
// sharedValueName: 'translateX',
// type: 'repeat',
// values: { from: 0, to: 200, current: 127.5 },
// callsite: { file: 'MyScreen.tsx', line: 42 },
// ...
// }
// ]
// }
// Get markdown for pasting into Claude/LLM
const markdown = getSnapshotMarkdown();
// Resume
resume();API
Core Functions
| Function | Returns | Description |
|----------|---------|-------------|
| isInstalled() | boolean | Check if Babel plugin is configured |
| isPaused() | boolean | Check if animations are paused |
| pause() | PauseSnapshot | Pause all animations, return snapshot |
| resume() | void | Resume all animations |
| toggle() | PauseSnapshot \| null | Toggle pause state |
Snapshot Functions
snapshotNow(opts?: SnapshotOptions): PauseSnapshot
Take a snapshot of current animation state with optional filtering.
// All animations
snapshotNow()
// Filter by file
snapshotNow({ filterFile: 'MyScreen.tsx' })
// Filter by file + line proximity
snapshotNow({
filterFile: 'MyScreen.tsx',
filterLine: 42,
proximityThreshold: 200
})
// Limit results
snapshotNow({ maxAnimations: 5 })getSnapshotMarkdown(opts?: SnapshotOptions): string
Get snapshot as prompt-ready markdown.
const markdown = getSnapshotMarkdown({ filterFile: 'MyScreen.tsx' });
// ## Animation State at Pause
//
// ### `translateX` (repeat)
//
// | Property | Value |
// |----------|-------|
// | **Current** | `127.50` |
// | From → To | 0 → 200 |
// | Progress | 63.7% |
// | Location | `MyScreen.tsx:42` |getSnapshotJSON(opts?: SnapshotOptions): string
Get snapshot as JSON string for logging or export.
const json = getSnapshotJSON({ filterFile: 'MyScreen.tsx' });
console.log(json);
// {
// "timestamp": 1706123456789,
// "animations": [...],
// "totalCount": 3,
// "summary": "3 animation(s) captured"
// }SnapshotOptions
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| filterComponent | string | - | Partial match on React component name (case-insensitive) |
| filterFile | string | - | Partial match on callsite file path |
| filterLine | number | - | Center line for proximity filter (requires filterFile) |
| proximityThreshold | number | 200 | Max line distance from filterLine |
| maxAnimations | number | 20 | Maximum animations to return |
Types
interface PauseSnapshot {
timestamp: number;
timestampISO: string;
animations: AnimationSnapshot[];
totalCount: number;
summary: string;
}
interface AnimationSnapshot {
id: string;
type: 'timing' | 'spring' | 'decay' | 'repeat' | 'sequence' | 'delay';
sharedValueName?: string; // e.g., 'translateX', 'scale'
componentName?: string; // React component where animation is defined
callsite: {
file: string;
line: number;
column?: number;
};
values: {
from: number;
to: number;
current: number; // actual value at snapshot time
};
timing: {
startTime: number;
elapsed: number;
duration?: number;
progress?: number; // 0-100 for timing animations
};
config: { type: string; config: Record<string, any> };
description: string;
}Filtering
By Component Name
Find animations defined in a specific React component:
snapshotNow({ filterComponent: 'PulsingCircle' })Matching is case-insensitive and partial, so 'pulsing' would also match.
By File
Find animations defined in a specific file:
snapshotNow({ filterFile: 'MyScreen.tsx' })By File + Line Proximity
Narrow down to animations near a specific line:
snapshotNow({
filterFile: 'MyScreen.tsx',
filterLine: 42,
proximityThreshold: 200 // within 200 lines
})How it Works
- Babel plugin wraps animation factories (
withTiming,withSpring, etc.) to track metadata including component name - Runtime captures SharedValue references and registers animations with their source context
- On pause reads current values from SharedValues (synced from UI thread)
- Filtering matches by component name (primary), file path, or line proximity
Instrumented Functions
withTimingwithSpringwithDecaywithRepeatwithSequencewithDelay
Best Practices for Accurate Tracking
For the best animation tracking experience, follow these tips:
1. Use Named Function Components
The babel plugin captures the enclosing function name. Named components enable accurate filtering.
// Good - named component
function PulsingCircle() {
const scale = useSharedValue(1);
// ...
}
// Good - named const
const PulsingCircle = () => {
const scale = useSharedValue(1);
// ...
};
// Less ideal - anonymous inline
export default () => {
const scale = useSharedValue(1);
// ...
};2. Use Descriptive SharedValue Names
SharedValue variable names appear in snapshots, making debugging easier.
// Good - descriptive names
const translateX = useSharedValue(0);
const opacity = useSharedValue(1);
const scale = useSharedValue(1);
// Harder to debug
const sv1 = useSharedValue(0);
const val = useSharedValue(1);3. Keep Animations Close to Their SharedValues
The babel plugin tracks the file and line where animations are defined. Keep them together for accurate filtering.
// Good - animation near SharedValue
function MyComponent() {
const translateX = useSharedValue(0);
const animate = () => {
translateX.value = withSpring(100); // Line 5
};
}
// Split across files is harder to track
// animation-utils.ts
export const startAnimation = (sv) => {
sv.value = withSpring(100); // Tracked here, not in component
};4. Babel Plugin Order Matters
The plugin MUST run before Reanimated's plugin:
// babel.config.js
module.exports = {
plugins: [
'reanimated-pause-state/babel', // First!
'react-native-reanimated/plugin', // Last!
],
};5. SharedValues Passed as Props
When SharedValues are passed as props, animations are tracked at the call site:
function Parent() {
const scale = useSharedValue(1);
return <Child scale={scale} />;
}
function Child({ scale }) {
const animate = () => {
scale.value = withSpring(2); // Tracked as "Child" component
};
}Annotating Child will show the animation. Annotating Parent won't (the filter won't match, but you'll still see all animations with their SharedValue names).
Limitations
- Only affects animations created via instrumented factory calls
- Does not pause native animations (Lottie, navigation transitions)
- Dynamic
toValue(computed at runtime) won't be captured in config - Spring animations don't have duration/progress (physics-based)
Requirements
- React Native 0.70+
react-native-reanimated>= 3.0.0 (peer dependency)
// package.json
{
"dependencies": {
"react-native-reanimated": "^3.0.0",
"reanimated-pause-state": "^0.1.0"
}
}License
MIT
