React Native Reanimated DnDReact Native Reanimated DnD
Examples

Custom Animations

Create smooth and engaging animations using React Native Reanimated 4 with the drag and drop library.

Overview

The library integrates seamlessly with React Native Reanimated 4 to provide smooth, performant animations. You can customize how items animate during drag operations, when dropped, and during state transitions.

Key Features

  • Reanimated 4 Integration: Built-in support for worklets and shared values
  • Custom Animation Functions: Define your own animation behaviors
  • Performance Optimized: Runs on the UI thread for 60fps animations
  • Flexible Timing: Control duration, easing, and animation curves
  • State-Based Animations: Different animations for different drag states

Basic Implementation

import React, { useState } from "react";
import { View, Text, StyleSheet } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
  withTiming,
  withSequence,
  Easing,
} from "react-native-reanimated";
import {
  DropProvider,
  Draggable,
  Droppable,
} from "react-native-reanimated-dnd";

interface AnimatedItemData {
  id: string;
  label: string;
  color: string;
}

export function CustomAnimationsExample() {
  const [droppedItems, setDroppedItems] = useState<AnimatedItemData[]>([]);

  const items: AnimatedItemData[] = [
    { id: "1", label: "Spring", color: "#ff6b6b" },
    { id: "2", label: "Timing", color: "#4ecdc4" },
    { id: "3", label: "Sequence", color: "#45b7d1" },
  ];

  const handleDrop = (data: AnimatedItemData) => {
    setDroppedItems((prev) => [...prev, data]);
  };

  return (
    <GestureHandlerRootView style={styles.container}>
      <DropProvider>
        <View style={styles.content}>
          <Text style={styles.title}>Custom Animations</Text>
          <Text style={styles.subtitle}>
            Different animation styles for drag and drop interactions
          </Text>

          {/* Drop Zone */}
          <View style={styles.dropZoneArea}>
            <Droppable<AnimatedItemData>
              droppableId="animation-zone"
              onDrop={handleDrop}
              style={styles.dropZone}
              activeStyle={styles.activeDropZone}
            >
              <Text style={styles.dropZoneText}>Drop Zone</Text>
              <Text style={styles.dropZoneSubtext}>
                {droppedItems.length} items dropped
              </Text>
            </Droppable>
          </View>

          {/* Animated Draggable Items */}
          <View style={styles.draggableItemsArea}>
            {/* Spring Animation */}
            <Draggable<AnimatedItemData>
              data={items[0]}
              animationFunction={(toValue) => {
                "worklet";
                return withSpring(toValue, {
                  damping: 15,
                  stiffness: 150,
                  mass: 1,
                });
              }}
              style={[styles.draggable, { backgroundColor: items[0].color }]}
            >
              <View style={styles.itemContent}>
                <Text style={styles.itemLabel}>{items[0].label}</Text>
                <Text style={styles.itemHint}>Bouncy spring</Text>
              </View>
            </Draggable>

            {/* Timing Animation */}
            <Draggable<AnimatedItemData>
              data={items[1]}
              animationFunction={(toValue) => {
                "worklet";
                return withTiming(toValue, {
                  duration: 500,
                  easing: Easing.out(Easing.cubic),
                });
              }}
              style={[styles.draggable, { backgroundColor: items[1].color }]}
            >
              <View style={styles.itemContent}>
                <Text style={styles.itemLabel}>{items[1].label}</Text>
                <Text style={styles.itemHint}>Smooth timing</Text>
              </View>
            </Draggable>

            {/* Sequence Animation */}
            <Draggable<AnimatedItemData>
              data={items[2]}
              animationFunction={(toValue) => {
                "worklet";
                return withSequence(
                  withTiming(toValue * 1.1, { duration: 150 }),
                  withSpring(toValue, { damping: 12 })
                );
              }}
              style={[styles.draggable, { backgroundColor: items[2].color }]}
            >
              <View style={styles.itemContent}>
                <Text style={styles.itemLabel}>{items[2].label}</Text>
                <Text style={styles.itemHint}>Overshoot + spring</Text>
              </View>
            </Draggable>
          </View>

          {/* Animation Info */}
          <View style={styles.infoContainer}>
            <Text style={styles.infoTitle}>Animation Types:</Text>
            <View style={styles.infoItem}>
              <View
                style={[styles.infoIndicator, { backgroundColor: "#ff6b6b" }]}
              />
              <Text style={styles.infoText}>
                <Text style={styles.bold}>Spring:</Text> Natural bouncy motion
                with physics
              </Text>
            </View>
            <View style={styles.infoItem}>
              <View
                style={[styles.infoIndicator, { backgroundColor: "#4ecdc4" }]}
              />
              <Text style={styles.infoText}>
                <Text style={styles.bold}>Timing:</Text> Controlled duration
                with easing curves
              </Text>
            </View>
            <View style={styles.infoItem}>
              <View
                style={[styles.infoIndicator, { backgroundColor: "#45b7d1" }]}
              />
              <Text style={styles.infoText}>
                <Text style={styles.bold}>Sequence:</Text> Multiple animations
                chained together
              </Text>
            </View>
          </View>
        </View>
      </DropProvider>
    </GestureHandlerRootView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#000000",
  },
  content: {
    flex: 1,
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: "bold",
    color: "#FFFFFF",
    textAlign: "center",
    marginBottom: 8,
  },
  subtitle: {
    fontSize: 15,
    color: "#8E8E93",
    textAlign: "center",
    marginBottom: 30,
    lineHeight: 22,
  },
  dropZoneArea: {
    alignItems: "center",
    marginBottom: 40,
  },
  dropZone: {
    width: "80%",
    height: 120,
    borderWidth: 2,
    borderStyle: "dashed",
    borderColor: "#58a6ff",
    backgroundColor: "rgba(88, 166, 255, 0.08)",
    borderRadius: 16,
    justifyContent: "center",
    alignItems: "center",
    padding: 16,
  },
  activeDropZone: {
    borderColor: "#FFFFFF",
    backgroundColor: "rgba(255, 255, 255, 0.1)",
    transform: [{ scale: 1.02 }],
  },
  dropZoneText: {
    fontSize: 16,
    fontWeight: "600",
    color: "#FFFFFF",
    marginBottom: 4,
  },
  dropZoneSubtext: {
    fontSize: 12,
    color: "#8E8E93",
  },
  draggableItemsArea: {
    gap: 20,
    marginBottom: 40,
  },
  draggable: {
    width: 160,
    height: 80,
    borderRadius: 12,
    shadowColor: "#000",
    shadowOffset: { width: 0, height: 4 },
    shadowOpacity: 0.3,
    shadowRadius: 6,
    elevation: 8,
    alignSelf: "center",
  },
  itemContent: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    padding: 12,
  },
  itemLabel: {
    fontSize: 16,
    fontWeight: "bold",
    color: "#FFFFFF",
    marginBottom: 4,
  },
  itemHint: {
    fontSize: 12,
    color: "rgba(255, 255, 255, 0.8)",
    textAlign: "center",
  },
  infoContainer: {
    backgroundColor: "#1a1a1a",
    borderRadius: 12,
    padding: 16,
    borderLeftWidth: 4,
    borderLeftColor: "#58a6ff",
  },
  infoTitle: {
    fontSize: 16,
    fontWeight: "bold",
    color: "#FFFFFF",
    marginBottom: 12,
  },
  infoItem: {
    flexDirection: "row",
    alignItems: "center",
    gap: 12,
    marginBottom: 8,
  },
  infoIndicator: {
    width: 12,
    height: 12,
    borderRadius: 6,
  },
  infoText: {
    fontSize: 14,
    color: "#8E8E93",
    flex: 1,
    lineHeight: 20,
  },
  bold: {
    fontWeight: "bold",
    color: "#FFFFFF",
  },
});

Animation Types

Spring Animations

Natural, physics-based animations with bounce:

<Draggable
  data={itemData}
  animationFunction={(toValue) => {
    "worklet";
    return withSpring(toValue, {
      damping: 15, // Controls bounce (lower = more bounce)
      stiffness: 150, // Controls speed (higher = faster)
      mass: 1, // Controls weight (higher = slower)
    });
  }}
>
  {/* Content */}
</Draggable>

Timing Animations

Controlled duration with easing curves:

<Draggable
  data={itemData}
  animationFunction={(toValue) => {
    "worklet";
    return withTiming(toValue, {
      duration: 300,
      easing: Easing.out(Easing.cubic),
    });
  }}
>
  {/* Content */}
</Draggable>

Sequence Animations

Chain multiple animations together:

<Draggable
  data={itemData}
  animationFunction={(toValue) => {
    "worklet";
    return withSequence(
      withTiming(toValue * 1.2, { duration: 100 }), // Overshoot
      withSpring(toValue, { damping: 12 }) // Settle
    );
  }}
>
  {/* Content */}
</Draggable>

Advanced Animation Patterns

State-Based Animations

Different animations based on drag state:

function StatefulAnimatedDraggable({ data }) {
  const [dragState, setDragState] = useState("idle");

  const getAnimationFunction = () => {
    switch (dragState) {
      case "dragging":
        return (toValue) => {
          "worklet";
          return withTiming(toValue, { duration: 100 }); // Fast response
        };
      case "dropped":
        return (toValue) => {
          "worklet";
          return withSpring(toValue, { damping: 20 }); // Bouncy settle
        };
      default:
        return (toValue) => {
          "worklet";
          return withSpring(toValue); // Default spring
        };
    }
  };

  return (
    <Draggable
      data={data}
      animationFunction={getAnimationFunction()}
      onDragStart={() => setDragState("dragging")}
      onDragEnd={() => setDragState("idle")}
      onStateChange={(state) => {
        if (state === "DROPPED") setDragState("dropped");
      }}
    >
      {/* Content */}
    </Draggable>
  );
}

Gesture-Driven Animations

Animations that respond to gesture velocity:

function VelocityDraggable({ data }) {
  const velocity = useSharedValue(0);

  const dynamicAnimation = (toValue) => {
    "worklet";
    const speed = Math.abs(velocity.value);

    if (speed > 1000) {
      // Fast gesture - quick animation
      return withTiming(toValue, { duration: 150 });
    } else if (speed > 500) {
      // Medium gesture - spring animation
      return withSpring(toValue, { damping: 15 });
    } else {
      // Slow gesture - gentle spring
      return withSpring(toValue, { damping: 25 });
    }
  };

  return (
    <Draggable
      data={data}
      animationFunction={dynamicAnimation}
      onDragging={({ tx, ty }) => {
        // Calculate velocity (simplified)
        velocity.value = Math.sqrt(tx * tx + ty * ty);
      }}
    >
      {/* Content */}
    </Draggable>
  );
}

Custom Easing Functions

Create unique animation curves:

const customEasing = {
  // Elastic easing
  elastic: (t) => {
    "worklet";
    return Math.sin(((13 * Math.PI) / 2) * t) * Math.pow(2, 10 * (t - 1));
  },

  // Bounce easing
  bounce: (t) => {
    "worklet";
    if (t < 1 / 2.75) {
      return 7.5625 * t * t;
    } else if (t < 2 / 2.75) {
      return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75;
    } else if (t < 2.5 / 2.75) {
      return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375;
    } else {
      return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;
    }
  },
};

<Draggable
  data={itemData}
  animationFunction={(toValue) => {
    "worklet";
    return withTiming(toValue, {
      duration: 600,
      easing: customEasing.bounce,
    });
  }}
>
  {/* Content */}
</Draggable>;

Visual Effects

Scale and Rotation

Add scale and rotation during drag:

function ScaleRotateDraggable({ data }) {
  const scale = useSharedValue(1);
  const rotation = useSharedValue(0);

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }, { rotate: `${rotation.value}deg` }],
  }));

  return (
    <Draggable
      data={data}
      onDragStart={() => {
        scale.value = withSpring(1.1);
        rotation.value = withSpring(5);
      }}
      onDragEnd={() => {
        scale.value = withSpring(1);
        rotation.value = withSpring(0);
      }}
    >
      <Animated.View style={[styles.draggable, animatedStyle]}>
        {/* Content */}
      </Animated.View>
    </Draggable>
  );
}

Opacity and Shadow Effects

Dynamic opacity and shadow during interactions:

function ShadowDraggable({ data }) {
  const opacity = useSharedValue(1);
  const shadowOpacity = useSharedValue(0.3);

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

  return (
    <Draggable
      data={data}
      onDragStart={() => {
        opacity.value = withTiming(0.8);
        shadowOpacity.value = withTiming(0.6);
      }}
      onDragEnd={() => {
        opacity.value = withTiming(1);
        shadowOpacity.value = withTiming(0.3);
      }}
    >
      <Animated.View style={[styles.draggable, animatedStyle]}>
        {/* Content */}
      </Animated.View>
    </Draggable>
  );
}

Performance Optimization

Worklet Functions

Always use worklets for animation functions:

// Good - runs on UI thread
const optimizedAnimation = (toValue) => {
  "worklet";
  return withSpring(toValue);
};

// Bad - runs on JS thread
const slowAnimation = (toValue) => {
  return withSpring(toValue);
};

Minimize Re-renders

Use shared values to avoid component re-renders:

function OptimizedDraggable({ data }) {
  // Use shared values for animations
  const scale = useSharedValue(1);
  const opacity = useSharedValue(1);

  // Combine animations in single style
  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],
    opacity: opacity.value,
  }));

  return (
    <Draggable data={data}>
      <Animated.View style={[styles.draggable, animatedStyle]}>
        {/* Content */}
      </Animated.View>
    </Draggable>
  );
}

Conditional Animations

Only animate when necessary:

function ConditionalAnimationDraggable({ data, enableAnimations }) {
  const getAnimationFunction = () => {
    if (!enableAnimations) {
      return (toValue) => {
        "worklet";
        return toValue; // No animation
      };
    }

    return (toValue) => {
      "worklet";
      return withSpring(toValue);
    };
  };

  return (
    <Draggable data={data} animationFunction={getAnimationFunction()}>
      {/* Content */}
    </Draggable>
  );
}

Common Animation Patterns

Card Flip Animation

const flipAnimation = (toValue) => {
  "worklet";
  return withSequence(
    withTiming(toValue * 0.5, { duration: 150 }),
    withTiming(toValue, { duration: 150 })
  );
};

Elastic Bounce

const elasticBounce = (toValue) => {
  "worklet";
  return withSpring(toValue, {
    damping: 8,
    stiffness: 100,
    mass: 0.8,
  });
};

Smooth Slide

const smoothSlide = (toValue) => {
  "worklet";
  return withTiming(toValue, {
    duration: 250,
    easing: Easing.out(Easing.quad),
  });
};

Best Practices

  1. Use Worklets: Always mark animation functions with 'worklet'
  2. Optimize Performance: Minimize re-renders and complex calculations
  3. Match Context: Choose animations that fit your app's design language
  4. Test on Device: Animations may behave differently on real devices
  5. Provide Feedback: Use animations to communicate state changes

Troubleshooting

Common Issues

Animations not working:

  • Ensure 'worklet' directive is present
  • Check that Reanimated is properly installed
  • Verify animation function returns a value

Poor performance:

  • Use shared values instead of state
  • Avoid complex calculations in worklets
  • Test on lower-end devices

Inconsistent behavior:

  • Check for conflicting animations
  • Ensure proper cleanup in useEffect
  • Verify timing and duration values

Next Steps