| title | High-Performance Animations |
|---|---|
| impact | MEDIUM |
| tags | reanimated, animations, worklets, ui-thread |
Use React Native Reanimated for smooth 60+ FPS animations.
Incorrect (JS thread - blocks on heavy work):
const opacity = useRef(new Animated.Value(0)).current;
Animated.timing(opacity, { toValue: 1 }).start();Correct (UI thread - smooth even during JS work):
const opacity = useSharedValue(0);
const style = useAnimatedStyle(() => ({ opacity: opacity.value }));
opacity.value = withTiming(1);- Animations drop frames or feel janky
- UI freezes during animations
- Need gesture-driven animations
- Want animations to run during heavy JS work
react-native-reanimated(v4+) andreact-native-workletsinstalled
npm install react-native-reanimated react-native-workletsAdd to babel.config.js:
module.exports = {
plugins: ['react-native-worklets/plugin'], // Must be last
};Note: Reanimated 4 requires React Native's New Architecture (Fabric + TurboModules). The Legacy Architecture is no longer supported. If upgrading from v3, see the migration notes at the end of this document.
- Main/UI Thread: Handles native rendering (60+ FPS target)
- JS Thread: Runs React and your JavaScript
Problem: Heavy JS work blocks animations running on JS thread.
Solution: Run animations on UI thread with Reanimated worklets.
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming
} from 'react-native-reanimated';
const FadeInView = () => {
const opacity = useSharedValue(0);
// This runs on UI thread - won't be blocked by JS
const animatedStyle = useAnimatedStyle(() => {
return { opacity: opacity.value };
});
useEffect(() => {
opacity.value = withTiming(1, { duration: 500 });
}, []);
return <Animated.View style={[styles.box, animatedStyle]} />;
};import { scheduleOnUI } from 'react-native-worklets';
const triggerAnimation = () => {
scheduleOnUI(() => {
'worklet';
console.log('Running on UI thread');
// Direct UI manipulations here
});
};import { scheduleOnRN } from 'react-native-worklets';
// Regular JS function
const trackAnalytics = (value) => {
analytics.track('animation_complete', { value });
};
const AnimatedComponent = () => {
const progress = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => {
// When animation completes, call JS function
if (progress.value === 1) {
scheduleOnRN(trackAnalytics, progress.value);
}
return { opacity: progress.value };
});
return <Animated.View style={animatedStyle} />;
};import { scheduleOnRN } from 'react-native-worklets';
const AnimatedButton = () => {
const scale = useSharedValue(1);
const onComplete = () => {
console.log('Animation finished!');
};
const handlePress = () => {
scale.value = withTiming(
1.2,
{ duration: 200 },
(finished) => {
if (finished) {
scheduleOnRN(onComplete);
}
}
);
};
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
return (
<Pressable onPress={handlePress}>
<Animated.View style={[styles.button, animatedStyle]}>
<Text>Press Me</Text>
</Animated.View>
</Pressable>
);
};| Thread | Best For |
|---|---|
| UI Thread (worklets) | Visual animations, transforms, gestures |
| JS Thread | State updates, data processing, API calls |
| Hook/API | Use Case |
|---|---|
useAnimatedStyle |
Animated styles (auto UI thread) |
scheduleOnUI |
Manual UI thread execution (from react-native-worklets) |
scheduleOnRN |
Call JS functions from worklets (from react-native-worklets) |
useTransition |
Alternative for React state-driven delays |
- Accessing React state in worklets: Use
useSharedValueinstead ofuseStatefor animated values - Not using Animated components: Must use
Animated.View,Animated.Text, etc. - Heavy computation in useAnimatedStyle: Keep worklets fast
- Forgetting 'worklet' directive: Required for inline worklet functions
// BAD: Regular function in useAnimatedStyle
const style = useAnimatedStyle(() => {
heavyComputation(); // Blocks UI thread!
return { opacity: 1 };
});
// GOOD: Keep worklets fast
const style = useAnimatedStyle(() => {
return { opacity: opacity.value }; // Just read value
});If you're upgrading from Reanimated 3.x, here are the key changes.
Can't upgrade to v4? If your project is blocked from migrating to New Architecture (e.g., incompatible native libraries, complex native code, or timeline constraints), keep using existing APIs and leverage native drivers where applicable. Avoid introducing legacy Reanimated 3.x or older to reduce future migration complexity.
| Old API (v3) | New API (v4) | Package |
|---|---|---|
runOnUI(() => {...})() |
scheduleOnUI(() => {...}) |
react-native-worklets |
runOnJS(fn)(args) |
scheduleOnRN(fn, args) |
react-native-worklets |
executeOnUIRuntimeSync |
runOnUISync |
react-native-worklets |
runOnRuntime |
scheduleOnRuntime |
react-native-worklets |
useScrollViewOffset |
useScrollOffset |
react-native-reanimated |
useWorkletCallback |
Use useCallback with 'worklet'; directive |
React |
useAnimatedGestureHandler- Migrate to the Gesture API fromreact-native-gesture-handlerv2+addWhitelistedNativeProps/addWhitelistedUIProps- No longer neededcombineTransition- UseEntryExitTransition.entering(...).exiting(...)instead
// Before (v3)
withSpring(value, {
restDisplacementThreshold: 0.01,
restSpeedThreshold: 0.01,
duration: 300,
});
// After (v4)
withSpring(value, {
energyThreshold: 0.01, // Replaces both threshold parameters
duration: 200, // Duration is now "perceptual" (~1.5x actual time)
});- Enable New Architecture - Reanimated 4 only supports Fabric + TurboModules
- Install
react-native-worklets- Required new dependency - Update Babel plugin - Change
'react-native-reanimated/plugin'to'react-native-worklets/plugin' - Update imports - Move worklet functions to
react-native-worklets - Update API calls - New functions take callback + args directly (not curried)
- Rebuild native apps - Required after adding
react-native-worklets
- js-measure-fps.md - Verify animation frame rate
- js-bottomsheet.md - Keep bottom sheet visual state on the UI thread
- js-concurrent-react.md - React-level deferral with useTransition