Claude Code Plugins

Community-maintained marketplace

Feedback
0
1

React Native animation patterns using reanimated v4. Use when implementing animations, transitions, gestures, or micro-interactions.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name reanimated-library
description React Native animation patterns using reanimated v4. Use when implementing animations, transitions, gestures, or micro-interactions.

Reanimated Library

This skill provides react-native-reanimated v4 animation patterns for native-performance animations in GalliGo Mobile.

When to Use This Skill

  • Implementing animations and transitions
  • Creating gesture interactions (swipe, drag, pinch)
  • Building micro-interactions (button press, toggle)
  • Adding scroll-based effects (parallax, fade)
  • Optimizing animation performance

Core Concepts

Shared Values

Foundation of reanimated - values that can be animated:

import { useSharedValue } from 'react-native-reanimated'

const Component = () => {
  const opacity = useSharedValue(1)
  const translateY = useSharedValue(0)
  const scale = useSharedValue(1)

  // Modify shared values
  opacity.value = 0.5
  translateY.value = 100

  return <Animated.View>{/* Content */}</Animated.View>
}

Animated Styles

Connect shared values to component styles:

import Animated, { useAnimatedStyle } from 'react-native-reanimated'

const Component = () => {
  const opacity = useSharedValue(1)

  const animatedStyle = useAnimatedStyle(() => ({
    opacity: opacity.value,
    transform: [
      { translateY: translateY.value },
      { scale: scale.value },
    ],
  }))

  return <Animated.View style={animatedStyle}>{/* Content */}</Animated.View>
}

Spring Animations

Natural, bouncy animations (preferred for iOS):

import { withSpring } from 'react-native-reanimated'

const bounceIn = () => {
  opacity.value = withSpring(1, {
    damping: 15,
    stiffness: 150,
  })
}

// Common spring configs
const gentleSpring = { damping: 20, stiffness: 120 } // Modals
const snappySpring = { damping: 10, stiffness: 200 } // Buttons
const bouncySpring = { damping: 8, stiffness: 180 }  // Playful

Timing Animations

Controlled, smooth animations:

import { withTiming, Easing } from 'react-native-reanimated'

const fadeOut = () => {
  opacity.value = withTiming(0, {
    duration: 300,
    easing: Easing.out(Easing.ease),
  })
}

// Common timing configs
const quick = { duration: 200 }
const standard = { duration: 300 }
const slow = { duration: 500 }

iOS Timing Standards

Follow iOS Human Interface Guidelines for timing:

Navigation Transitions

// Screen push/pop: 350ms spring
const screenTransition = () => {
  translateX.value = withSpring(0, {
    damping: 18,
    stiffness: 140,
  })
}

// Modal presentation: 440ms spring
const presentModal = () => {
  translateY.value = withSpring(0, {
    damping: 22,
    stiffness: 130,
  })
}

// Modal dismissal: 320ms timing
const dismissModal = () => {
  translateY.value = withTiming(-screenHeight, {
    duration: 320,
    easing: Easing.in(Easing.ease),
  })
}

Micro-interactions

// Button press: 200-300ms
const buttonPress = () => {
  scale.value = withTiming(0.95, { duration: 100 })
  // On release:
  scale.value = withSpring(1, { damping: 12, stiffness: 200 })
}

// Toggle: 250ms
const toggle = () => {
  position.value = withSpring(isOn ? 20 : 0, {
    damping: 15,
    stiffness: 180,
    duration: 250,
  })
}

Common Animation Patterns

1. Fade In/Out

import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'

// Using built-in animations
<Animated.View
  entering={FadeIn.duration(300)}
  exiting={FadeOut.duration(200)}
>
  {/* Content */}
</Animated.View>

// Custom fade
const Component = () => {
  const opacity = useSharedValue(0)

  useEffect(() => {
    opacity.value = withTiming(1, { duration: 300 })
  }, [])

  const animatedStyle = useAnimatedStyle(() => ({
    opacity: opacity.value,
  }))

  return <Animated.View style={animatedStyle}>{/* Content */}</Animated.View>
}

2. Slide In/Out

import Animated, { SlideInDown, SlideOutDown } from 'react-native-reanimated'

// Modal/sheet sliding up from bottom
<Animated.View
  entering={SlideInDown.springify().damping(20)}
  exiting={SlideOutDown.duration(320)}
  className="absolute bottom-0 w-full bg-white rounded-t-3xl"
>
  {/* Modal content */}
</Animated.View>

// Slide from right (for lists)
<Animated.View
  entering={SlideInRight.duration(300).delay(index * 50)}
>
  {/* List item */}
</Animated.View>

3. Scale Button Press

const Button = ({ onPress, children }) => {
  const scale = useSharedValue(1)

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],
  }))

  const handlePressIn = () => {
    scale.value = withTiming(0.95, { duration: 100 })
  }

  const handlePressOut = () => {
    scale.value = withSpring(1, { damping: 12, stiffness: 200 })
  }

  return (
    <Pressable
      onPressIn={handlePressIn}
      onPressOut={handlePressOut}
      onPress={onPress}
    >
      <Animated.View style={animatedStyle}>{children}</Animated.View>
    </Pressable>
  )
}

4. Scroll-Based Parallax

import { useAnimatedScrollHandler } from 'react-native-reanimated'

const ParallaxScreen = () => {
  const scrollY = useSharedValue(0)

  const scrollHandler = useAnimatedScrollHandler({
    onScroll: (event) => {
      scrollY.value = event.contentOffset.y
    },
  })

  const headerStyle = useAnimatedStyle(() => ({
    transform: [
      { translateY: scrollY.value * 0.5 }, // Parallax
    ],
    opacity: 1 - scrollY.value / 200, // Fade out
  }))

  return (
    <Animated.ScrollView onScroll={scrollHandler} scrollEventThrottle={16}>
      <Animated.View style={headerStyle} className="h-64">
        {/* Header image */}
      </Animated.View>
      {/* Content */}
    </Animated.ScrollView>
  )
}

5. Skeleton Loading

const SkeletonLoader = () => {
  const opacity = useSharedValue(0.3)

  useEffect(() => {
    opacity.value = withRepeat(
      withSequence(
        withTiming(1, { duration: 1000 }),
        withTiming(0.3, { duration: 1000 })
      ),
      -1, // Infinite
      false // No reverse
    )
  }, [])

  const animatedStyle = useAnimatedStyle(() => ({
    opacity: opacity.value,
  }))

  return (
    <Animated.View style={animatedStyle} className="h-4 bg-gray-200 rounded" />
  )
}

6. Staggered List Entry

const AnimatedList = ({ items }) => (
  <View>
    {items.map((item, index) => (
      <Animated.View
        key={item.id}
        entering={FadeIn.duration(300).delay(index * 50)}
      >
        <ItemCard item={item} />
      </Animated.View>
    ))}
  </View>
)

Gesture Handling

Requires react-native-gesture-handler.

Pan Gesture (Drag)

import { Gesture, GestureDetector } from 'react-native-gesture-handler'
import Animated, { useSharedValue, useAnimatedStyle } from 'react-native-reanimated'

const DraggableCard = () => {
  const translateX = useSharedValue(0)
  const translateY = useSharedValue(0)
  const context = useSharedValue({ x: 0, y: 0 })

  const panGesture = Gesture.Pan()
    .onStart(() => {
      context.value = { x: translateX.value, y: translateY.value }
    })
    .onUpdate((event) => {
      translateX.value = context.value.x + event.translationX
      translateY.value = context.value.y + event.translationY
    })
    .onEnd(() => {
      // Snap back to origin
      translateX.value = withSpring(0)
      translateY.value = withSpring(0)
    })

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [
      { translateX: translateX.value },
      { translateY: translateY.value },
    ],
  }))

  return (
    <GestureDetector gesture={panGesture}>
      <Animated.View style={animatedStyle}>{/* Content */}</Animated.View>
    </GestureDetector>
  )
}

Swipe to Delete

const SwipeToDelete = ({ onDelete, children }) => {
  const translateX = useSharedValue(0)

  const panGesture = Gesture.Pan()
    .activeOffsetX([-10, 10]) // Ignore small movements
    .onUpdate((event) => {
      // Only allow left swipe
      if (event.translationX < 0) {
        translateX.value = event.translationX
      }
    })
    .onEnd((event) => {
      if (event.translationX < -100) {
        // Threshold reached - delete
        translateX.value = withTiming(-500, { duration: 300 })
        runOnJS(onDelete)()
      } else {
        // Snap back
        translateX.value = withSpring(0)
      }
    })

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ translateX: translateX.value }],
  }))

  return (
    <GestureDetector gesture={panGesture}>
      <Animated.View style={animatedStyle}>{children}</Animated.View>
    </GestureDetector>
  )
}

Long Press

const LongPressCard = ({ onLongPress }) => {
  const scale = useSharedValue(1)

  const longPressGesture = Gesture.LongPress()
    .minDuration(500)
    .onStart(() => {
      scale.value = withSpring(0.95)
      runOnJS(onLongPress)()
    })
    .onFinalize(() => {
      scale.value = withSpring(1)
    })

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],
  }))

  return (
    <GestureDetector gesture={longPressGesture}>
      <Animated.View style={animatedStyle}>{/* Content */}</Animated.View>
    </GestureDetector>
  )
}

Pinch to Zoom

const ZoomableImage = ({ uri }) => {
  const scale = useSharedValue(1)
  const savedScale = useSharedValue(1)

  const pinchGesture = Gesture.Pinch()
    .onUpdate((event) => {
      scale.value = savedScale.value * event.scale
    })
    .onEnd(() => {
      savedScale.value = scale.value
      // Reset if zoomed out too much
      if (scale.value < 1) {
        scale.value = withSpring(1)
        savedScale.value = 1
      }
    })

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],
  }))

  return (
    <GestureDetector gesture={pinchGesture}>
      <Animated.Image source={{ uri }} style={animatedStyle} />
    </GestureDetector>
  )
}

Layout Animations

Automatically animate layout changes:

import Animated, { Layout } from 'react-native-reanimated'

const AnimatedList = ({ items }) => (
  <View>
    {items.map((item) => (
      <Animated.View
        key={item.id}
        entering={FadeIn}
        exiting={FadeOut}
        layout={Layout.springify()} // Animate layout changes
      >
        <ItemCard item={item} />
      </Animated.View>
    ))}
  </View>
)

Advanced Patterns

Interpolation

Map input range to output range:

import { interpolate } from 'react-native-reanimated'

const animatedStyle = useAnimatedStyle(() => {
  const rotate = interpolate(
    scrollY.value,
    [0, 100],
    [0, 180], // Rotate from 0° to 180° as scroll from 0 to 100
    'clamp' // Don't exceed bounds
  )

  return {
    transform: [{ rotate: `${rotate}deg` }],
  }
})

withSequence (Chain Animations)

import { withSequence } from 'react-native-reanimated'

const bounce = () => {
  scale.value = withSequence(
    withTiming(1.2, { duration: 150 }),
    withSpring(1, { damping: 12 })
  )
}

withRepeat (Loop Animations)

import { withRepeat } from 'react-native-reanimated'

useEffect(() => {
  // Infinite rotation
  rotation.value = withRepeat(
    withTiming(360, { duration: 2000, easing: Easing.linear }),
    -1, // Infinite
    false // No reverse
  )
}, [])

Performance Optimization

Use Worklets

Worklets run on the UI thread for 60 FPS:

'worklet' // This function runs on UI thread
const clamp = (value: number, min: number, max: number) => {
  'worklet'
  return Math.min(Math.max(value, min), max)
}

const animatedStyle = useAnimatedStyle(() => {
  const clamped = clamp(translateX.value, -100, 100)
  return {
    transform: [{ translateX: clamped }],
  }
})

runOnJS for JS Functions

Call JS functions from worklets:

import { runOnJS } from 'react-native-reanimated'

const panGesture = Gesture.Pan()
  .onEnd(() => {
    // ❌ BAD: Direct call won't work
    onComplete()

    // ✅ GOOD: Wrap with runOnJS
    runOnJS(onComplete)()
  })

Avoid Heavy Calculations

// ❌ BAD: Heavy calculation in animated style
const animatedStyle = useAnimatedStyle(() => ({
  opacity: expensiveCalculation(scrollY.value),
}))

// ✅ GOOD: Pre-calculate or use useDerivedValue
const opacity = useDerivedValue(() => {
  return expensiveCalculation(scrollY.value)
})

const animatedStyle = useAnimatedStyle(() => ({
  opacity: opacity.value,
}))

Built-in Animations

Quick animations without writing code:

import Animated, {
  FadeIn,
  FadeOut,
  SlideInRight,
  SlideOutLeft,
  ZoomIn,
  ZoomOut,
  BounceIn,
} from 'react-native-reanimated'

<Animated.View entering={FadeIn} exiting={FadeOut}>
  {/* Fades in and out */}
</Animated.View>

<Animated.View entering={SlideInRight.duration(300)}>
  {/* Slides in from right in 300ms */}
</Animated.View>

<Animated.View entering={BounceIn.springify()}>
  {/* Bounces in with spring */}
</Animated.View>

Available animations:

  • Fade: FadeIn, FadeOut, FadeInDown, FadeInUp
  • Slide: SlideInLeft, SlideInRight, SlideInUp, SlideInDown
  • Zoom: ZoomIn, ZoomOut
  • Bounce: BounceIn, BounceOut
  • Flip: FlipInXUp, FlipInYLeft

Common Gotchas

1. Shared Values Don't Trigger Re-renders

// ❌ WRONG: Won't update UI
const [count, setCount] = useState(0)
opacity.value = count / 100 // Component won't re-render

// ✅ CORRECT: Use in animated style
const animatedStyle = useAnimatedStyle(() => ({
  opacity: count / 100,
}))

2. Can't Use Regular setState in Worklets

// ❌ WRONG: setState doesn't work in worklets
const panGesture = Gesture.Pan()
  .onEnd(() => {
    setIsComplete(true) // Won't work
  })

// ✅ CORRECT: Use runOnJS
const panGesture = Gesture.Pan()
  .onEnd(() => {
    runOnJS(setIsComplete)(true)
  })

3. Must Use Animated Components

// ❌ WRONG: Regular View won't animate
<View style={animatedStyle} />

// ✅ CORRECT: Use Animated.View
<Animated.View style={animatedStyle} />

Quick Reference

Animation Type Use Case Duration
withSpring Buttons, modals, natural motion ~200-350ms
withTiming Fades, slides, controlled 200-500ms
withSequence Multi-step animations Varies
withRepeat Loading, infinite loops Infinite
Built-in (FadeIn, etc.) Quick animations Customizable

Related Skills

  • @ios-design-guidelines - iOS timing standards
  • @react-native-patterns - Component integration
  • @mobile-accessibility - Accessible animations

For complex animations, use @animation-specialist agent. This skill is for quick reference and common patterns.