Skip to main content

Animation Functions

Custom animation functions for controlling how draggable items animate during and after drag operations.

Overview

Animation functions allow you to customize how draggable items animate when they return to their original position or snap to a drop zone. The library uses React Native Reanimated for smooth, performant animations that run on the UI thread.

AnimationFunction Type

type AnimationFunction = (toValue: number) => number;

Animation functions are worklets that take a target value and return an animated value using Reanimated's animation functions.

Parameters

  • toValue: The target value to animate to (typically 0 for returning to original position)

Returns

An animated value created using Reanimated animation functions like withSpring, withTiming, etc.

Built-in Animation Patterns

Spring Animations

Spring animations provide natural, bouncy motion that feels responsive and organic.

Basic Spring

const springAnimation = (toValue) => {
'worklet';
return withSpring(toValue);
};

<Draggable
data={data}
animationFunction={springAnimation}
>
<Text>Spring animation</Text>
</Draggable>

Custom Spring Configuration

const customSpring = (toValue) => {
'worklet';
return withSpring(toValue, {
damping: 15, // Controls oscillation (higher = less bouncy)
stiffness: 150, // Controls speed (higher = faster)
mass: 1, // Controls inertia (higher = slower)
overshootClamping: false, // Allow overshooting
restDisplacementThreshold: 0.01,
restSpeedThreshold: 2,
});
};

Bouncy Spring

const bouncySpring = (toValue) => {
'worklet';
return withSpring(toValue, {
damping: 8, // Low damping for more bounce
stiffness: 100,
mass: 0.8,
});
};

Gentle Spring

const gentleSpring = (toValue) => {
'worklet';
return withSpring(toValue, {
damping: 20, // High damping for smooth motion
stiffness: 120,
mass: 1.2,
});
};

Timing Animations

Timing animations provide precise control over duration and easing curves.

Basic Timing

const timingAnimation = (toValue) => {
'worklet';
return withTiming(toValue, {
duration: 300,
easing: Easing.out(Easing.cubic),
});
};

Elastic Timing

import { Easing } from 'react-native-reanimated';

const elasticTiming = (toValue) => {
'worklet';
return withTiming(toValue, {
duration: 600,
easing: Easing.elastic(2), // Elastic effect
});
};

Bounce Timing

const bounceTiming = (toValue) => {
'worklet';
return withTiming(toValue, {
duration: 800,
easing: Easing.bounce,
});
};

Smooth Timing

const smoothTiming = (toValue) => {
'worklet';
return withTiming(toValue, {
duration: 250,
easing: Easing.bezier(0.25, 0.1, 0.25, 1), // Material Design curve
});
};

Advanced Animation Patterns

Sequential Animations

const sequentialAnimation = (toValue) => {
'worklet';
return withSequence(
withTiming(toValue * 1.1, { duration: 100 }), // Slight overshoot
withSpring(toValue, { damping: 15 }) // Spring back
);
};

Delayed Animations

const delayedAnimation = (toValue) => {
'worklet';
return withDelay(
200, // 200ms delay
withSpring(toValue)
);
};

Repeated Animations

const pulseAnimation = (toValue) => {
'worklet';
return withRepeat(
withSequence(
withTiming(toValue * 0.95, { duration: 150 }),
withTiming(toValue, { duration: 150 })
),
3, // Repeat 3 times
false // Don't reverse
);
};

Conditional Animations

const conditionalAnimation = (toValue) => {
'worklet';
// Different animation based on target value
if (toValue === 0) {
// Returning to original position - use spring
return withSpring(toValue, { damping: 15 });
} else {
// Snapping to drop zone - use timing
return withTiming(toValue, { duration: 200 });
}
};

Context-Aware Animations

State-Based Animations

function StatefulDraggable({ data, isSuccess }) {
const getAnimation = useCallback((toValue) => {
'worklet';
if (isSuccess) {
// Success animation - bouncy and celebratory
return withSequence(
withTiming(toValue * 1.2, { duration: 100 }),
withSpring(toValue, { damping: 8, stiffness: 150 })
);
} else {
// Default animation - smooth return
return withSpring(toValue, { damping: 15 });
}
}, [isSuccess]);

return (
<Draggable
data={data}
animationFunction={getAnimation}
>
<Text>State-aware animation</Text>
</Draggable>
);
}

Performance-Based Animations

function PerformanceDraggable({ data, isLowEndDevice }) {
const getAnimation = useCallback((toValue) => {
'worklet';
if (isLowEndDevice) {
// Simple, fast animation for low-end devices
return withTiming(toValue, { duration: 150 });
} else {
// Rich animation for high-end devices
return withSequence(
withTiming(toValue * 1.1, { duration: 100 }),
withSpring(toValue, { damping: 12, stiffness: 200 })
);
}
}, [isLowEndDevice]);

return (
<Draggable
data={data}
animationFunction={getAnimation}
>
<Text>Performance-aware animation</Text>
</Draggable>
);
}

Animation Presets

Material Design Animations

// Material Design motion curves
const materialAnimations = {
standard: (toValue) => {
'worklet';
return withTiming(toValue, {
duration: 300,
easing: Easing.bezier(0.4, 0.0, 0.2, 1),
});
},

decelerate: (toValue) => {
'worklet';
return withTiming(toValue, {
duration: 250,
easing: Easing.bezier(0.0, 0.0, 0.2, 1),
});
},

accelerate: (toValue) => {
'worklet';
return withTiming(toValue, {
duration: 200,
easing: Easing.bezier(0.4, 0.0, 1, 1),
});
},
};

iOS-Style Animations

const iOSAnimations = {
default: (toValue) => {
'worklet';
return withSpring(toValue, {
damping: 20,
stiffness: 300,
mass: 1,
});
},

gentle: (toValue) => {
'worklet';
return withTiming(toValue, {
duration: 350,
easing: Easing.out(Easing.cubic),
});
},

snappy: (toValue) => {
'worklet';
return withSpring(toValue, {
damping: 15,
stiffness: 400,
mass: 0.8,
});
},
};

Game-Style Animations

const gameAnimations = {
powerUp: (toValue) => {
'worklet';
return withSequence(
withTiming(toValue * 1.3, { duration: 100 }),
withTiming(toValue * 0.9, { duration: 100 }),
withSpring(toValue, { damping: 10 })
);
},

impact: (toValue) => {
'worklet';
return withSequence(
withTiming(toValue * 0.8, { duration: 50 }),
withSpring(toValue, { damping: 8, stiffness: 200 })
);
},

float: (toValue) => {
'worklet';
return withRepeat(
withSequence(
withTiming(toValue - 5, { duration: 1000 }),
withTiming(toValue + 5, { duration: 1000 })
),
-1, // Infinite
true // Reverse
);
},
};

Dynamic Animation Selection

Animation Factory

class AnimationFactory {
static create(type: string, options: any = {}) {
const animations = {
spring: (toValue) => {
'worklet';
return withSpring(toValue, {
damping: options.damping || 15,
stiffness: options.stiffness || 150,
...options
});
},

timing: (toValue) => {
'worklet';
return withTiming(toValue, {
duration: options.duration || 300,
easing: options.easing || Easing.out(Easing.cubic),
...options
});
},

bounce: (toValue) => {
'worklet';
return withTiming(toValue, {
duration: options.duration || 600,
easing: Easing.bounce,
...options
});
},

elastic: (toValue) => {
'worklet';
return withTiming(toValue, {
duration: options.duration || 800,
easing: Easing.elastic(options.elasticity || 2),
...options
});
},
};

return animations[type] || animations.spring;
}
}

// Usage
function ConfigurableDraggable({ animationType, animationOptions }) {
const animation = AnimationFactory.create(animationType, animationOptions);

return (
<Draggable
data={data}
animationFunction={animation}
>
<Text>Configurable animation</Text>
</Draggable>
);
}

Theme-Based Animations

const useThemeAnimations = (theme) => {
return useMemo(() => {
const animations = {
light: {
gentle: (toValue) => {
'worklet';
return withSpring(toValue, { damping: 20, stiffness: 200 });
},
quick: (toValue) => {
'worklet';
return withTiming(toValue, { duration: 200 });
},
},

dark: {
smooth: (toValue) => {
'worklet';
return withTiming(toValue, {
duration: 300,
easing: Easing.out(Easing.cubic)
});
},
dramatic: (toValue) => {
'worklet';
return withSequence(
withTiming(toValue * 1.2, { duration: 150 }),
withSpring(toValue, { damping: 10 })
);
},
},

playful: {
bounce: (toValue) => {
'worklet';
return withSpring(toValue, { damping: 8, stiffness: 150 });
},
wiggle: (toValue) => {
'worklet';
return withSequence(
withTiming(toValue + 5, { duration: 50 }),
withTiming(toValue - 5, { duration: 50 }),
withSpring(toValue)
);
},
},
};

return animations[theme] || animations.light;
}, [theme]);
};

Performance Considerations

Optimized Animations

// Prefer spring animations for better performance
const optimizedSpring = (toValue) => {
'worklet';
return withSpring(toValue, {
damping: 15,
stiffness: 150,
// Reduce precision for better performance
restDisplacementThreshold: 0.1,
restSpeedThreshold: 5,
});
};

// Use shorter durations for timing animations
const optimizedTiming = (toValue) => {
'worklet';
return withTiming(toValue, {
duration: 200, // Shorter duration
easing: Easing.out(Easing.quad), // Simpler easing
});
};

Conditional Performance

const adaptiveAnimation = (toValue) => {
'worklet';
// Use simpler animations during heavy operations
const isHeavyOperation = global.isHeavyOperation || false;

if (isHeavyOperation) {
return withTiming(toValue, { duration: 100 });
} else {
return withSpring(toValue, { damping: 15 });
}
};

Testing Animations

Animation Testing Utilities

const createTestAnimation = (duration = 100) => {
return (toValue) => {
'worklet';
return withTiming(toValue, { duration });
};
};

// Use in tests
function TestDraggable() {
const isTest = process.env.NODE_ENV === 'test';
const animation = isTest
? createTestAnimation(0) // Instant for tests
: springAnimation; // Normal for app

return (
<Draggable
data={data}
animationFunction={animation}
>
<Text>Test-friendly animation</Text>
</Draggable>
);
}

Best Practices

Animation Guidelines

  1. Use spring animations for natural feel
  2. Keep durations under 500ms for responsiveness
  3. Match animations to context (gentle for forms, bouncy for games)
  4. Test on low-end devices to ensure smooth performance
  5. Provide reduced motion options for accessibility

Common Patterns

// Good: Contextual animation selection
const getContextualAnimation = (context) => {
if (context.isForm) {
return gentleSpring;
} else if (context.isGame) {
return bouncySpring;
} else {
return standardSpring;
}
};

// Good: Performance-aware animations
const getPerformanceAnimation = (deviceCapability) => {
return deviceCapability === 'high'
? richAnimation
: simpleAnimation;
};

// Avoid: Overly complex animations
const avoidComplexAnimation = (toValue) => {
'worklet';
// Too many nested animations can hurt performance
return withSequence(
withRepeat(withTiming(toValue * 1.1, { duration: 50 }), 5),
withDelay(100, withSpring(toValue)),
withRepeat(withTiming(toValue * 0.9, { duration: 25 }), 3)
);
};

See Also