┌───────────────────────────────┐ │ Spring and Decay Animations │ ├───────────────────────────────┤ │ │ │ ○ │ │ │ │ Drag the circle left or right │ │ Release to see spring or decay│ └───────────────────────────────┘
Spring and decay animations in React Native - Mini App: Build & Ship
import React, { useRef } from 'react'; import { View, Text, Animated, PanResponder, StyleSheet } from 'react-native'; export default function SpringDecayAnimationScreen() { const translateX = useRef(new Animated.Value(0)).current; const panResponder = useRef( PanResponder.create({ onStartShouldSetPanResponder: () => true, onPanResponderMove: (e, gestureState) => { // TODO: Update translateX with gestureState.dx }, onPanResponderRelease: (e, gestureState) => { // TODO: Animate translateX with spring or decay based on velocity }, }) ).current; return ( <View style={styles.container}> <Animated.View style={[styles.circle, { transform: [{ translateX }] }]} {...panResponder.panHandlers} /> <Text style={styles.instructions}> Drag the circle left or right{' '}Release to see spring or decay </Text> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#fff', }, circle: { width: 80, height: 80, borderRadius: 40, backgroundColor: '#4a90e2', }, instructions: { marginTop: 20, fontSize: 16, color: '#333', textAlign: 'center', }, });
import React, { useRef } from 'react'; import { View, Text, Animated, PanResponder, StyleSheet } from 'react-native'; export default function SpringDecayAnimationScreen() { const translateX = useRef(new Animated.Value(0)).current; const panResponder = useRef( PanResponder.create({ onStartShouldSetPanResponder: () => true, onPanResponderMove: (e, gestureState) => { translateX.setValue(gestureState.dx); }, onPanResponderRelease: (e, gestureState) => { const velocity = gestureState.vx; if (Math.abs(velocity) < 0.5) { Animated.spring(translateX, { toValue: 0, useNativeDriver: false, bounciness: 10, }).start(); } else { Animated.decay(translateX, { velocity: velocity, deceleration: 0.997, useNativeDriver: false, }).start(); } }, }) ).current; return ( <View style={styles.container}> <Animated.View style={[styles.circle, { transform: [{ translateX }] }]} {...panResponder.panHandlers} accessibilityLabel="Draggable circle" accessible={true} /> <Text style={styles.instructions} accessibilityRole="text"> Drag the circle left or right{' '}Release to see spring or decay </Text> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#fff', }, circle: { width: 80, height: 80, borderRadius: 40, backgroundColor: '#4a90e2', }, instructions: { marginTop: 20, fontSize: 16, color: '#333', textAlign: 'center', }, });
This solution uses React Native's Animated API to create a draggable circle that responds to user gestures.
We use PanResponder to track the horizontal drag movement and update the translateX animated value accordingly.
When the user releases the drag, we check the horizontal velocity (gestureState.vx). If the velocity is small (less than 0.5), we animate the circle back to the center using a spring animation, which gives a natural bounce effect.
If the velocity is larger, we use a decay animation that simulates momentum, letting the circle continue moving and gradually slow down.
We set useNativeDriver: false because the translation affects layout and gesture handling.
Accessibility labels are added for screen readers, and the UI is simple and clear for easy interaction.
┌───────────────────────────────┐ │ Spring and Decay Animations │ ├───────────────────────────────┤ │ │ │ ○ │ │ │ │ Drag the circle left or right │ │ Release to see spring or decay│ └───────────────────────────────┘