From harness-claude
Creates fluid 60fps React Native animations using Reanimated shared values, worklets, interpolate, and layout animations for micro-interactions, transitions, gestures, and lists.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Create fluid 60fps animations with React Native Reanimated using shared values, worklets, and layout animations
Optimizes Framer Motion animations in React via 42 rules across bundle size, re-renders, layout transitions, gestures, scroll effects, springs, and SVGs. Use when writing, reviewing, or refactoring.
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.
Provides React Native patterns for StyleSheet styling, Flexbox layouts, React Navigation, Gesture Handler, and Reanimated 3 animations in cross-platform mobile apps.
Share bugs, ideas, or general feedback.
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/