install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/mobile-animation-patterns" ~/.claude/skills/intense-visions-harness-engineering-mobile-animation-patterns-0f47eb && rm -rf "$T"
manifest:
agents/skills/codex/mobile-animation-patterns/SKILL.mdsource content
Mobile Animation Patterns
Create fluid 60fps animations with React Native Reanimated using shared values, worklets, and layout animations
When to Use
- Building micro-interactions (button press feedback, toggle animations)
- Animating screen transitions, modals, or bottom sheets
- Creating gesture-driven animations (swipe cards, draggable elements)
- Implementing layout animations (list item enters/exits, accordion expand)
- Replacing
API for better performanceAnimated
Instructions
- Use Reanimated's shared values instead of React state for animation values. Shared values live on the UI thread and update without crossing the JS bridge.
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated'; function AnimatedBox() { const scale = useSharedValue(1); const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }], })); return ( <Pressable onPressIn={() => { scale.value = withSpring(0.95); }} onPressOut={() => { scale.value = withSpring(1); }} > <Animated.View style={[styles.box, animatedStyle]} /> </Pressable> ); }
- Choose the right animation function:
— linear or eased animation with fixed durationwithTiming(target, config)
— physics-based spring animation (natural feel)withSpring(target, config)
— momentum-based deceleration (fling gestures)withDecay(config)
— run animations in orderwithSequence(...)
— delay before startingwithDelay(ms, animation)
— loop an animationwithRepeat(animation, count, reverse)
// Bounce in opacity.value = withTiming(1, { duration: 300, easing: Easing.out(Easing.cubic) }); // Springy scale scale.value = withSpring(1, { damping: 15, stiffness: 150 }); // Shake effect translateX.value = withSequence( withTiming(-10, { duration: 50 }), withRepeat(withTiming(10, { duration: 100 }), 3, true), withTiming(0, { duration: 50 }) ); // Pulse animation opacity.value = withRepeat( withSequence(withTiming(0.5, { duration: 500 }), withTiming(1, { duration: 500 })), -1, // infinite true );
- Use
to map shared values to styles. This hook creates a style object that updates on the UI thread.useAnimatedStyle
const animatedStyle = useAnimatedStyle(() => ({ opacity: opacity.value, transform: [ { translateY: interpolate(progress.value, [0, 1], [50, 0]) }, { scale: interpolate(progress.value, [0, 1], [0.8, 1]) }, ], }));
- Use
to map values between ranges.interpolate
import { interpolate, Extrapolation } from 'react-native-reanimated'; const animatedStyle = useAnimatedStyle(() => ({ opacity: interpolate(scrollY.value, [0, 100], [1, 0], Extrapolation.CLAMP), height: interpolate(scrollY.value, [0, 100], [200, 60], Extrapolation.CLAMP), }));
- Use layout animations for enter/exit transitions. Reanimated provides built-in entering and exiting animations that work with conditional rendering.
import Animated, { FadeIn, FadeOut, SlideInRight, Layout } from 'react-native-reanimated'; function NotificationList({ items }: { items: Notification[] }) { return ( <View> {items.map((item) => ( <Animated.View key={item.id} entering={SlideInRight.duration(300)} exiting={FadeOut.duration(200)} layout={Layout.springify()} > <NotificationCard notification={item} /> </Animated.View> ))} </View> ); }
- Use
for scroll-driven animations.useAnimatedScrollHandler
const scrollY = useSharedValue(0); const scrollHandler = useAnimatedScrollHandler({ onScroll: (event) => { scrollY.value = event.contentOffset.y; }, }); const headerStyle = useAnimatedStyle(() => ({ height: interpolate(scrollY.value, [0, 150], [200, 60], Extrapolation.CLAMP), opacity: interpolate(scrollY.value, [0, 100], [1, 0], Extrapolation.CLAMP), })); return ( <> <Animated.View style={[styles.header, headerStyle]} /> <Animated.ScrollView onScroll={scrollHandler} scrollEventThrottle={16}> {/* content */} </Animated.ScrollView> </> );
- Use
to compute values from other shared values.useDerivedValue
const progress = useSharedValue(0); const opacity = useDerivedValue(() => interpolate(progress.value, [0, 1], [0.3, 1]));
- Run callbacks when animations complete with
callback orwithTiming
.runOnJS
scale.value = withSpring(0, {}, (finished) => { if (finished) { runOnJS(onAnimationComplete)(); } });
Details
Why Reanimated over the built-in
API: The built-in Animated
Animated runs on the JS thread by default (useNativeDriver: true offloads only transform and opacity). Reanimated runs all animation logic on the UI thread via worklets, supporting any style property at 60fps.
Worklets: Functions marked with
'worklet'; directive run on the UI thread. useAnimatedStyle, useAnimatedScrollHandler, and gesture callbacks are implicitly worklets. Use runOnJS() to call back to JavaScript from a worklet.
Spring configuration:
(default 10): Higher = less bouncy, lower = more oscillationdamping
(default 100): Higher = faster, snappier animationstiffness
(default 1): Higher = heavier, slower to start/stopmass- Good defaults for UI:
(snappy with slight overshoot){ damping: 15, stiffness: 150 }
Performance rules:
- Never read
of a shared value in the render function (only in worklets and animated styles).value - Avoid creating new shared values in loops or conditional blocks
- Use
before starting a new animation on the same valuecancelAnimation(sharedValue) - Prefer
andtransform
— they are GPU-composited and avoid layout recalculationopacity
Source
https://docs.swmansion.com/react-native-reanimated/
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.