From harness-claude
Creates fluid 60fps animations using React Native Reanimated shared values, worklets, and layout animations for micro-interactions, gestures, and screen transitions.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:mobile-animation-patternsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Create fluid 60fps animations with React Native Reanimated using shared values, worklets, and layout animations
Create fluid 60fps animations with React Native Reanimated using shared values, worklets, and layout animations
Animated API for better performanceimport 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>
);
}
withTiming(target, config) — linear or eased animation with fixed durationwithSpring(target, config) — physics-based spring animation (natural feel)withDecay(config) — momentum-based deceleration (fling gestures)withSequence(...) — run animations in orderwithDelay(ms, animation) — delay before startingwithRepeat(animation, count, reverse) — loop an animation// 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
);
useAnimatedStyle to map shared values to styles. This hook creates a style object that updates on the UI thread.const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
transform: [
{ translateY: interpolate(progress.value, [0, 1], [50, 0]) },
{ scale: interpolate(progress.value, [0, 1], [0.8, 1]) },
],
}));
interpolate to map values between ranges.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),
}));
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>
);
}
useAnimatedScrollHandler for scroll-driven animations.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>
</>
);
useDerivedValue to compute values from other shared values.const progress = useSharedValue(0);
const opacity = useDerivedValue(() => interpolate(progress.value, [0, 1], [0.3, 1]));
withTiming callback or runOnJS.scale.value = withSpring(0, {}, (finished) => {
if (finished) {
runOnJS(onAnimationComplete)();
}
});
Why Reanimated over the built-in Animated API: The built-in 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:
damping (default 10): Higher = less bouncy, lower = more oscillationstiffness (default 100): Higher = faster, snappier animationmass (default 1): Higher = heavier, slower to start/stop{ damping: 15, stiffness: 150 } (snappy with slight overshoot)Performance rules:
.value of a shared value in the render function (only in worklets and animated styles)cancelAnimation(sharedValue) before starting a new animation on the same valuetransform and opacity — they are GPU-composited and avoid layout recalculationhttps://docs.swmansion.com/react-native-reanimated/
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeProvides expert Framer Motion guidance for React: declarative animations with variants, gesture recognition (hover/tap/drag), layout transitions via layoutId, and AnimatePresence for enter/exit. Activates on framer-motion imports or motion components.
Creates smooth React/JavaScript animations with Motion/Framer Motion: motion components, variants, gestures (hover/tap/drag), layout/exit animations, springs, scroll effects. For interactive UIs, micro-interactions, transitions.
Implements physics-based animations in React using React Spring's spring dynamics, gesture support, and Popmotion's composable utilities for fluid UI, inertia scrolling, and gesture-driven interfaces.