CtrlK
CommunityDocumentationLog inGet started
Tessl Logo

tessl/npm-react-native

A framework for building native apps using React

Overall
score

100%

Evaluation100%

1.06x

Agent success when using this tile

Overview
Eval results
Files

animation.mddocs/

React Native Animation & Interaction

React Native provides powerful animation and interaction APIs for creating smooth, engaging user experiences with declarative animations and gesture handling.

Installation

npm install react-native

Core Animation API

Animated

The primary animation library providing declarative, composable animations with performance optimization.

// ESM
import {Animated} from 'react-native';

// CommonJS
const {Animated} = require('react-native');

// Basic animated value
const fadeAnim = new Animated.Value(0);

// Animate to value
Animated.timing(fadeAnim, {
  toValue: 1,
  duration: 1000,
  useNativeDriver: true, // Use native driver for performance
}).start();

// Basic fade in animation
function FadeInView({children, style, ...props}) {
  const fadeAnim = useRef(new Animated.Value(0)).current;
  
  useEffect(() => {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 1000,
      useNativeDriver: true,
    }).start();
  }, [fadeAnim]);
  
  return (
    <Animated.View
      style={[style, {opacity: fadeAnim}]}
      {...props}
    >
      {children}
    </Animated.View>
  );
}

// Multiple animated values
function SlideUpView({children}) {
  const slideAnim = useRef(new Animated.Value(50)).current;
  const fadeAnim = useRef(new Animated.Value(0)).current;
  
  useEffect(() => {
    Animated.parallel([
      Animated.timing(slideAnim, {
        toValue: 0,
        duration: 500,
        useNativeDriver: true,
      }),
      Animated.timing(fadeAnim, {
        toValue: 1,
        duration: 500,
        useNativeDriver: true,
      }),
    ]).start();
  }, []);
  
  return (
    <Animated.View
      style={{
        opacity: fadeAnim,
        transform: [{translateY: slideAnim}],
      }}
    >
      {children}
    </Animated.View>
  );
}

// Animated components
const AnimatedTouchable = Animated.createAnimatedComponent(TouchableOpacity);
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);

// Scale animation on press
function ScaleButton({children, onPress}) {
  const scaleAnim = useRef(new Animated.Value(1)).current;
  
  const handlePressIn = () => {
    Animated.spring(scaleAnim, {
      toValue: 0.95,
      useNativeDriver: true,
    }).start();
  };
  
  const handlePressOut = () => {
    Animated.spring(scaleAnim, {
      toValue: 1,
      useNativeDriver: true,
    }).start();
  };
  
  return (
    <AnimatedTouchable
      onPressIn={handlePressIn}
      onPressOut={handlePressOut}
      onPress={onPress}
      style={{
        transform: [{scale: scaleAnim}],
      }}
    >
      {children}
    </AnimatedTouchable>
  );
}

// Interpolation for complex animations
function RotatingView({children}) {
  const rotateAnim = useRef(new Animated.Value(0)).current;
  
  useEffect(() => {
    Animated.loop(
      Animated.timing(rotateAnim, {
        toValue: 1,
        duration: 2000,
        useNativeDriver: true,
      })
    ).start();
  }, []);
  
  const rotate = rotateAnim.interpolate({
    inputRange: [0, 1],
    outputRange: ['0deg', '360deg'],
  });
  
  return (
    <Animated.View style={{transform: [{rotate}]}}>
      {children}
    </Animated.View>
  );
}

// Color interpolation
function ColorChangingView() {
  const colorAnim = useRef(new Animated.Value(0)).current;
  
  useEffect(() => {
    Animated.loop(
      Animated.sequence([
        Animated.timing(colorAnim, {
          toValue: 1,
          duration: 1000,
          useNativeDriver: false, // Color animations need JS driver
        }),
        Animated.timing(colorAnim, {
          toValue: 0,
          duration: 1000,
          useNativeDriver: false,
        }),
      ])
    ).start();
  }, []);
  
  const backgroundColor = colorAnim.interpolate({
    inputRange: [0, 1],
    outputRange: ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'],
  });
  
  return (
    <Animated.View style={{backgroundColor, width: 100, height: 100}} />
  );
}

// Chained animations
function ChainedAnimation() {
  const translateX = useRef(new Animated.Value(0)).current;
  const scale = useRef(new Animated.Value(1)).current;
  
  const startAnimation = () => {
    Animated.sequence([
      Animated.timing(translateX, {
        toValue: 100,
        duration: 500,
        useNativeDriver: true,
      }),
      Animated.spring(scale, {
        toValue: 1.5,
        useNativeDriver: true,
      }),
      Animated.delay(500),
      Animated.parallel([
        Animated.timing(translateX, {
          toValue: 0,
          duration: 500,
          useNativeDriver: true,
        }),
        Animated.spring(scale, {
          toValue: 1,
          useNativeDriver: true,
        }),
      ]),
    ]).start();
  };
  
  return (
    <View>
      <Animated.View
        style={{
          transform: [
            {translateX},
            {scale},
          ],
        }}
      >
        <Text>Animated Box</Text>
      </Animated.View>
      <Button title="Start Animation" onPress={startAnimation} />
    </View>
  );
}

// Value listeners
function AnimationWithCallback() {
  const slideAnim = useRef(new Animated.Value(0)).current;
  
  const animate = () => {
    slideAnim.setValue(0);
    
    Animated.timing(slideAnim, {
      toValue: 100,
      duration: 1000,
      useNativeDriver: true,
    }).start((finished) => {
      if (finished) {
        console.log('Animation completed');
      } else {
        console.log('Animation was interrupted');
      }
    });
  };
  
  // Listen to value changes
  useEffect(() => {
    const listener = slideAnim.addListener(({value}) => {
      console.log('Current value:', value);
    });
    
    return () => slideAnim.removeListener(listener);
  }, []);
  
  return (
    <View>
      <Animated.View
        style={{
          transform: [{translateX: slideAnim}],
        }}
      >
        <Text>Moving Box</Text>
      </Animated.View>
      <Button title="Animate" onPress={animate} />
    </View>
  );
}
// Animated Value types
interface AnimatedValue {
  constructor(value: number): AnimatedValue;
  setValue(value: number): void;
  setOffset(offset: number): void;
  flattenOffset(): void;
  extractOffset(): void;
  addListener(callback: (value: {value: number}) => void): string;
  removeListener(id: string): void;
  removeAllListeners(): void;
  stopAnimation(callback?: (value: number) => void): void;
  resetAnimation(callback?: (value: number) => void): void;
  interpolate(config: InterpolationConfig): AnimatedValue;
}

interface InterpolationConfig {
  inputRange: number[];
  outputRange: number[] | string[];
  easing?: (input: number) => number;
  extrapolate?: 'extend' | 'identity' | 'clamp';
  extrapolateLeft?: 'extend' | 'identity' | 'clamp';
  extrapolateRight?: 'extend' | 'identity' | 'clamp';
}

// Animation configuration types
interface TimingAnimationConfig {
  toValue: number;
  easing?: (input: number) => number;
  duration?: number;
  delay?: number;
  useNativeDriver?: boolean;
  isInteraction?: boolean;
}

interface SpringAnimationConfig {
  toValue: number;
  restDisplacementThreshold?: number;
  overshootClamping?: boolean;
  restSpeedThreshold?: number;
  velocity?: number;
  bounciness?: number;
  speed?: number;
  tension?: number;
  friction?: number;
  stiffness?: number;
  damping?: number;
  mass?: number;
  useNativeDriver?: boolean;
  isInteraction?: boolean;
}

interface DecayAnimationConfig {
  velocity: number;
  deceleration?: number;
  isInteraction?: boolean;
  useNativeDriver?: boolean;
}

// Animation composition types
interface CompositeAnimation {
  start(callback?: (finished: boolean) => void): void;
  stop(): void;
  reset(): void;
}

// Animated component types
interface AnimatedStatic {
  // Core values
  Value: typeof AnimatedValue;
  ValueXY: typeof AnimatedValueXY;
  
  // Animated components
  View: React.ComponentType<Animated.AnimatedProps<ViewProps>>;
  Text: React.ComponentType<Animated.AnimatedProps<TextProps>>;
  Image: React.ComponentType<Animated.AnimatedProps<ImageProps>>;
  ScrollView: React.ComponentType<Animated.AnimatedProps<ScrollViewProps>>;
  FlatList: React.ComponentType<Animated.AnimatedProps<FlatListProps<any>>>;
  SectionList: React.ComponentType<Animated.AnimatedProps<SectionListProps<any>>>;
  
  // Animation methods
  timing(value: AnimatedValue, config: TimingAnimationConfig): CompositeAnimation;
  spring(value: AnimatedValue, config: SpringAnimationConfig): CompositeAnimation;
  decay(value: AnimatedValue, config: DecayAnimationConfig): CompositeAnimation;
  
  // Composition methods
  parallel(animations: CompositeAnimation[], config?: {stopTogether?: boolean}): CompositeAnimation;
  sequence(animations: CompositeAnimation[]): CompositeAnimation;
  stagger(time: number, animations: CompositeAnimation[]): CompositeAnimation;
  delay(time: number): CompositeAnimation;
  loop(animation: CompositeAnimation, config?: {iterations?: number}): CompositeAnimation;
  
  // Utility methods
  add(a: AnimatedValue, b: AnimatedValue): AnimatedValue;
  subtract(a: AnimatedValue, b: AnimatedValue): AnimatedValue;
  divide(a: AnimatedValue, b: AnimatedValue): AnimatedValue;
  multiply(a: AnimatedValue, b: AnimatedValue): AnimatedValue;
  modulo(a: AnimatedValue, modulus: number): AnimatedValue;
  diffClamp(a: AnimatedValue, min: number, max: number): AnimatedValue;
  
  // Component creation
  createAnimatedComponent<T extends React.ComponentType<any>>(component: T): React.ComponentType<Animated.AnimatedProps<React.ComponentProps<T>>>;
  
  // Events
  event(argMapping: any[], config?: {useNativeDriver?: boolean; listener?: Function}): Function;
}

namespace Animated {
  interface AnimatedProps<T> {
    [K in keyof T]: K extends 'style'
      ? StyleProp<T[K]> | AnimatedValue | {[P in keyof T[K]]: T[K][P] | AnimatedValue}
      : T[K];
  }
}

Easing

Easing functions for smooth animation transitions and natural motion curves.

import {Easing} from 'react-native';

// Basic easing functions
const fadeIn = () => {
  Animated.timing(fadeAnim, {
    toValue: 1,
    duration: 1000,
    easing: Easing.ease, // Default easing
    useNativeDriver: true,
  }).start();
};

// Different easing curves
const easingExamples = {
  // Basic curves
  linear: Easing.linear,
  ease: Easing.ease,
  quad: Easing.quad,
  cubic: Easing.cubic,
  
  // Directional easing
  easeIn: Easing.in(Easing.quad),
  easeOut: Easing.out(Easing.quad),
  easeInOut: Easing.inOut(Easing.quad),
  
  // Bezier curves
  customBezier: Easing.bezier(0.25, 0.1, 0.25, 1),
  
  // Bounce and elastic
  bounce: Easing.bounce,
  elastic: Easing.elastic(1),
  
  // Stepped animation
  steps: Easing.step0,
  
  // Circle easing
  circle: Easing.circle,
  
  // Sine easing
  sine: Easing.sin,
  
  // Exponential easing
  expo: Easing.exp,
  
  // Back easing (overshoot)
  back: Easing.back(1.5),
  
  // Polynomial easing
  poly: Easing.poly(4),
};

// Custom easing function
const customEasing = (t) => {
  return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
};

// Easing comparison component
function EasingComparison() {
  const animations = useRef(
    Object.keys(easingExamples).reduce((acc, key) => {
      acc[key] = new Animated.Value(0);
      return acc;
    }, {})
  ).current;
  
  const startAnimations = () => {
    Object.keys(easingExamples).forEach((key) => {
      animations[key].setValue(0);
      
      Animated.timing(animations[key], {
        toValue: 200,
        duration: 2000,
        easing: easingExamples[key],
        useNativeDriver: true,
      }).start();
    });
  };
  
  return (
    <View>
      <Button title="Start Easing Comparison" onPress={startAnimations} />
      {Object.keys(easingExamples).map((key) => (
        <View key={key} style={styles.easingRow}>
          <Text style={styles.easingLabel}>{key}</Text>
          <Animated.View
            style={[
              styles.easingBox,
              {transform: [{translateX: animations[key]}]},
            ]}
          />
        </View>
      ))}
    </View>
  );
}

// Bouncy button with back easing
function BouncyButton({children, onPress}) {
  const scaleAnim = useRef(new Animated.Value(1)).current;
  
  const handlePress = () => {
    Animated.sequence([
      Animated.timing(scaleAnim, {
        toValue: 0.8,
        duration: 100,
        easing: Easing.in(Easing.back(2)),
        useNativeDriver: true,
      }),
      Animated.timing(scaleAnim, {
        toValue: 1,
        duration: 300,
        easing: Easing.out(Easing.back(2)),
        useNativeDriver: true,
      }),
    ]).start();
    
    onPress?.();
  };
  
  return (
    <AnimatedTouchable
      onPress={handlePress}
      style={{
        transform: [{scale: scaleAnim}],
      }}
    >
      {children}
    </AnimatedTouchable>
  );
}

// Progress indicator with stepped animation
function SteppedProgress({progress}) {
  const stepAnim = useRef(new Animated.Value(0)).current;
  
  useEffect(() => {
    Animated.timing(stepAnim, {
      toValue: progress,
      duration: 1000,
      easing: Easing.step0, // Stepped animation
      useNativeDriver: false,
    }).start();
  }, [progress]);
  
  const width = stepAnim.interpolate({
    inputRange: [0, 1],
    outputRange: ['0%', '100%'],
  });
  
  return (
    <View style={styles.progressContainer}>
      <Animated.View style={[styles.progressBar, {width}]} />
    </View>
  );
}
interface EasingStatic {
  // Basic easing functions
  linear: (t: number) => number;
  ease: (t: number) => number;
  quad: (t: number) => number;
  cubic: (t: number) => number;
  poly(n: number): (t: number) => number;
  sin: (t: number) => number;
  circle: (t: number) => number;
  exp: (t: number) => number;
  
  // Directional modifiers
  in(easing: (t: number) => number): (t: number) => number;
  out(easing: (t: number) => number): (t: number) => number;
  inOut(easing: (t: number) => number): (t: number) => number;
  
  // Special easing functions
  elastic(bounciness?: number): (t: number) => number;
  back(s?: number): (t: number) => number;
  bounce: (t: number) => number;
  
  // Bezier curve
  bezier(x1: number, y1: number, x2: number, y2: number): (t: number) => number;
  
  // Stepped animation
  step0: (t: number) => number;
  step1: (t: number) => number;
}

LayoutAnimation

Automatically animate layout changes without explicit animation code.

import {LayoutAnimation} from 'react-native';

// Basic layout animation
function AnimatedList() {
  const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
  
  const addItem = () => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
    setItems([...items, `Item ${items.length + 1}`]);
  };
  
  const removeItem = (index) => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
    setItems(items.filter((_, i) => i !== index));
  };
  
  return (
    <View>
      <Button title="Add Item" onPress={addItem} />
      {items.map((item, index) => (
        <TouchableOpacity
          key={index}
          onPress={() => removeItem(index)}
          style={styles.listItem}
        >
          <Text>{item}</Text>
        </TouchableOpacity>
      ))}
    </View>
  );
}

// Custom layout animation config
const customLayoutAnimation = {
  duration: 300,
  create: {
    type: LayoutAnimation.Types.easeInEaseOut,
    property: LayoutAnimation.Properties.opacity,
  },
  update: {
    type: LayoutAnimation.Types.easeInEaseOut,
  },
  delete: {
    type: LayoutAnimation.Types.easeInEaseOut,
    property: LayoutAnimation.Properties.opacity,
  },
};

function CustomAnimatedComponent() {
  const [isExpanded, setIsExpanded] = useState(false);
  
  const toggleExpanded = () => {
    LayoutAnimation.configureNext(customLayoutAnimation);
    setIsExpanded(!isExpanded);
  };
  
  return (
    <View>
      <TouchableOpacity onPress={toggleExpanded} style={styles.header}>
        <Text>Tap to expand</Text>
      </TouchableOpacity>
      
      {isExpanded && (
        <View style={styles.content}>
          <Text>This content animates in and out</Text>
          <Text>Using LayoutAnimation</Text>
        </View>
      )}
    </View>
  );
}

// Grid layout animation
function AnimatedGrid() {
  const [numColumns, setNumColumns] = useState(2);
  
  const toggleColumns = () => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
    setNumColumns(numColumns === 2 ? 3 : 2);
  };
  
  return (
    <View>
      <Button 
        title={`Switch to ${numColumns === 2 ? 3 : 2} columns`}
        onPress={toggleColumns}
      />
      
      <FlatList
        data={Array.from({length: 20}, (_, i) => ({id: i, title: `Item ${i}`}))}
        numColumns={numColumns}
        key={numColumns} // Force re-render for column change
        renderItem={({item}) => (
          <View style={[styles.gridItem, {flex: 1/numColumns}]}>
            <Text>{item.title}</Text>
          </View>
        )}
      />
    </View>
  );
}

// Layout animation with callbacks
function CallbackLayoutAnimation() {
  const [items, setItems] = useState(['A', 'B', 'C']);
  
  const shuffleItems = () => {
    LayoutAnimation.configureNext(
      LayoutAnimation.Presets.easeInEaseOut,
      () => {
        console.log('Layout animation finished');
      },
      (error) => {
        console.error('Layout animation failed:', error);
      }
    );
    
    const shuffled = [...items].sort(() => Math.random() - 0.5);
    setItems(shuffled);
  };
  
  return (
    <View>
      <Button title="Shuffle Items" onPress={shuffleItems} />
      {items.map((item, index) => (
        <View key={item} style={styles.shuffleItem}>
          <Text>{item}</Text>
        </View>
      ))}
    </View>
  );
}

// Enable layout animations on Android
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
  UIManager.setLayoutAnimationEnabledExperimental(true);
}
interface LayoutAnimationStatic {
  // Configure next layout change
  configureNext(
    config: LayoutAnimationConfig,
    onAnimationDidEnd?: () => void,
    onAnimationDidFail?: (error: any) => void
  ): void;
  
  // Preset configurations
  Presets: {
    easeInEaseOut: LayoutAnimationConfig;
    linear: LayoutAnimationConfig;
    spring: LayoutAnimationConfig;
  };
  
  // Animation types
  Types: {
    spring: string;
    linear: string;
    easeInEaseOut: string;
    easeIn: string;
    easeOut: string;
    keyboard: string;
  };
  
  // Animation properties
  Properties: {
    opacity: string;
    scaleX: string;
    scaleY: string;
    scaleXY: string;
  };
}

interface LayoutAnimationConfig {
  duration?: number;
  create?: LayoutAnimationAnim;
  update?: LayoutAnimationAnim;
  delete?: LayoutAnimationAnim;
}

interface LayoutAnimationAnim {
  duration?: number;
  delay?: number;
  springDamping?: number;
  initialVelocity?: number;
  type?: string;
  property?: string;
}

Gesture and Interaction APIs

PanResponder

Handle complex gesture recognition and touch events for custom interactive components.

import {PanResponder} from 'react-native';

// Basic draggable component
function DraggableView({children}) {
  const pan = useRef(new Animated.ValueXY()).current;
  
  const panResponder = useRef(
    PanResponder.create({
      onMoveShouldSetPanResponder: () => true,
      onPanResponderGrant: () => {
        pan.setOffset({
          x: pan.x._value,
          y: pan.y._value,
        });
      },
      onPanResponderMove: Animated.event([
        null,
        {dx: pan.x, dy: pan.y},
      ], {useNativeDriver: false}),
      onPanResponderRelease: () => {
        pan.flattenOffset();
      },
    })
  ).current;
  
  return (
    <Animated.View
      style={{
        transform: [{translateX: pan.x}, {translateY: pan.y}],
      }}
      {...panResponder.panHandlers}
    >
      {children}
    </Animated.View>
  );
}

// Swipe-to-dismiss component
function SwipeToDismiss({children, onDismiss}) {
  const translateX = useRef(new Animated.Value(0)).current;
  const [dismissed, setDismissed] = useState(false);
  
  const panResponder = useRef(
    PanResponder.create({
      onMoveShouldSetPanResponder: (_, gestureState) => {
        return Math.abs(gestureState.dx) > Math.abs(gestureState.dy);
      },
      
      onPanResponderMove: (_, gestureState) => {
        translateX.setValue(gestureState.dx);
      },
      
      onPanResponderRelease: (_, gestureState) => {
        const threshold = 120;
        
        if (Math.abs(gestureState.dx) > threshold) {
          // Dismiss
          const toValue = gestureState.dx > 0 ? 300 : -300;
          
          Animated.timing(translateX, {
            toValue,
            duration: 200,
            useNativeDriver: true,
          }).start(() => {
            setDismissed(true);
            onDismiss?.();
          });
        } else {
          // Return to original position
          Animated.spring(translateX, {
            toValue: 0,
            useNativeDriver: true,
          }).start();
        }
      },
    })
  ).current;
  
  if (dismissed) return null;
  
  return (
    <Animated.View
      style={{
        transform: [{translateX}],
      }}
      {...panResponder.panHandlers}
    >
      {children}
    </Animated.View>
  );
}

// Scalable and rotatable view
function ScalableRotatableView({children}) {
  const scale = useRef(new Animated.Value(1)).current;
  const rotate = useRef(new Animated.Value(0)).current;
  
  const lastScale = useRef(1);
  const lastRotate = useRef(0);
  
  const panResponder = useRef(
    PanResponder.create({
      onMoveShouldSetPanResponder: () => true,
      onMoveShouldSetPanResponderCapture: () => true,
      
      onPanResponderGrant: () => {
        lastScale.current = scale._value;
        lastRotate.current = rotate._value;
      },
      
      onPanResponderMove: (evt, gestureState) => {
        // Multi-touch gestures would require additional logic
        // This is a simplified version for single touch
        
        // Scale based on vertical movement
        const newScale = lastScale.current + gestureState.dy / 200;
        scale.setValue(Math.max(0.5, Math.min(2, newScale)));
        
        // Rotate based on horizontal movement
        const newRotate = lastRotate.current + gestureState.dx / 100;
        rotate.setValue(newRotate);
      },
      
      onPanResponderRelease: () => {
        // Optionally snap back to default values
        Animated.parallel([
          Animated.spring(scale, {
            toValue: 1,
            useNativeDriver: true,
          }),
          Animated.spring(rotate, {
            toValue: 0,
            useNativeDriver: true,
          }),
        ]).start();
      },
    })
  ).current;
  
  const rotateStr = rotate.interpolate({
    inputRange: [-1, 1],
    outputRange: ['-45deg', '45deg'],
  });
  
  return (
    <Animated.View
      style={{
        transform: [
          {scale},
          {rotate: rotateStr},
        ],
      }}
      {...panResponder.panHandlers}
    >
      {children}
    </Animated.View>
  );
}

// Pull-to-refresh with PanResponder
function CustomPullToRefresh({children, onRefresh}) {
  const translateY = useRef(new Animated.Value(0)).current;
  const [isRefreshing, setIsRefreshing] = useState(false);
  
  const panResponder = useRef(
    PanResponder.create({
      onMoveShouldSetPanResponder: (_, gestureState) => {
        return gestureState.dy > 0 && gestureState.vy > 0;
      },
      
      onPanResponderMove: (_, gestureState) => {
        if (gestureState.dy > 0 && !isRefreshing) {
          translateY.setValue(Math.min(gestureState.dy, 100));
        }
      },
      
      onPanResponderRelease: (_, gestureState) => {
        if (gestureState.dy > 60 && !isRefreshing) {
          setIsRefreshing(true);
          
          Animated.timing(translateY, {
            toValue: 60,
            duration: 200,
            useNativeDriver: true,
          }).start();
          
          // Simulate refresh
          onRefresh?.().finally(() => {
            setIsRefreshing(false);
            
            Animated.timing(translateY, {
              toValue: 0,
              duration: 300,
              useNativeDriver: true,
            }).start();
          });
        } else {
          Animated.timing(translateY, {
            toValue: 0,
            duration: 200,
            useNativeDriver: true,
          }).start();
        }
      },
    })
  ).current;
  
  return (
    <View style={{flex: 1}}>
      <Animated.View
        style={{
          position: 'absolute',
          top: -50,
          left: 0,
          right: 0,
          height: 50,
          justifyContent: 'center',
          alignItems: 'center',
          transform: [{translateY}],
        }}
      >
        {isRefreshing ? (
          <ActivityIndicator size="small" />
        ) : (
          <Text>Pull to refresh</Text>
        )}
      </Animated.View>
      
      <Animated.View
        style={{
          flex: 1,
          transform: [{translateY}],
        }}
        {...panResponder.panHandlers}
      >
        {children}
      </Animated.View>
    </View>
  );
}
interface PanResponderStatic {
  create(config: PanResponderConfig): PanResponderInstance;
}

interface PanResponderInstance {
  panHandlers: {
    onStartShouldSetResponder: (evt: GestureResponderEvent) => boolean;
    onMoveShouldSetResponder: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
    onResponderGrant: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
    onResponderMove: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
    onResponderRelease: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
    onResponderTerminate: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
    onStartShouldSetResponderCapture: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
    onMoveShouldSetResponderCapture: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
  };
}

interface PanResponderConfig {
  onStartShouldSetPanResponder?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
  onMoveShouldSetPanResponder?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
  onPanResponderGrant?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
  onPanResponderMove?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
  onPanResponderRelease?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
  onPanResponderTerminate?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
  onPanResponderReject?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
  onStartShouldSetPanResponderCapture?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
  onMoveShouldSetPanResponderCapture?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
  onPanResponderStart?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
  onPanResponderEnd?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
  onShouldBlockNativeResponder?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
}

interface PanResponderGestureState {
  stateID: number;
  moveX: number;
  moveY: number;
  x0: number;
  y0: number;
  dx: number;
  dy: number;
  vx: number;
  vy: number;
  numberActiveTouches: number;
}

InteractionManager

Schedule work after interactions have completed to maintain smooth animations and user experience.

import {InteractionManager} from 'react-native';

// Defer expensive operations after interactions
function ExpensiveComponent() {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  
  useEffect(() => {
    // Show loading state immediately
    setIsLoading(true);
    
    // Wait for interactions to complete, then load data
    const interactionPromise = InteractionManager.runAfterInteractions(() => {
      // Expensive operation
      return fetchLargeDataset().then(result => {
        setData(result);
        setIsLoading(false);
      });
    });
    
    return () => {
      interactionPromise.cancel();
    };
  }, []);
  
  if (isLoading) {
    return <ActivityIndicator size="large" />;
  }
  
  return (
    <FlatList
      data={data}
      renderItem={({item}) => <ExpensiveListItem item={item} />}
    />
  );
}

// Navigation transition with deferred loading
function NavigationScreen({route}) {
  const [content, setContent] = useState(null);
  
  useEffect(() => {
    // Run after navigation animation completes
    InteractionManager.runAfterInteractions(() => {
      loadScreenContent(route.params.id).then(setContent);
    });
  }, [route.params.id]);
  
  return (
    <View style={styles.container}>
      <Header title="Screen Title" />
      {content ? (
        <Content data={content} />
      ) : (
        <LoadingSkeleton />
      )}
    </View>
  );
}

// Custom hook for deferred execution
function useAfterInteractions(callback, deps = []) {
  const [isReady, setIsReady] = useState(false);
  
  useEffect(() => {
    let cancelled = false;
    
    const handle = InteractionManager.runAfterInteractions(() => {
      if (!cancelled) {
        callback();
        setIsReady(true);
      }
    });
    
    return () => {
      cancelled = true;
      handle.cancel();
    };
  }, deps);
  
  return isReady;
}

// Usage with custom hook
function DeferredComponent() {
  const [heavyData, setHeavyData] = useState(null);
  
  const isReady = useAfterInteractions(() => {
    processHeavyData().then(setHeavyData);
  }, []);
  
  return (
    <View>
      {isReady && heavyData ? (
        <HeavyDataView data={heavyData} />
      ) : (
        <PlaceholderView />
      )}
    </View>
  );
}

// Creating interaction handles manually
function InteractiveAnimation() {
  const scaleAnim = useRef(new Animated.Value(1)).current;
  const [isInteracting, setIsInteracting] = useState(false);
  
  const startAnimation = () => {
    // Create interaction handle
    const handle = InteractionManager.createInteractionHandle();
    setIsInteracting(true);
    
    Animated.sequence([
      Animated.timing(scaleAnim, {
        toValue: 1.2,
        duration: 500,
        useNativeDriver: true,
      }),
      Animated.timing(scaleAnim, {
        toValue: 1,
        duration: 500,
        useNativeDriver: true,
      }),
    ]).start(() => {
      // Clear interaction handle when animation completes
      InteractionManager.clearInteractionHandle(handle);
      setIsInteracting(false);
    });
  };
  
  return (
    <View>
      <Animated.View style={{transform: [{scale: scaleAnim}]}}>
        <TouchableOpacity onPress={startAnimation}>
          <Text>Animate</Text>
        </TouchableOpacity>
      </Animated.View>
      
      {isInteracting && (
        <Text>Animation in progress...</Text>
      )}
    </View>
  );
}

// Batch processing with interaction management
function BatchProcessor({items}) {
  const [processedItems, setProcessedItems] = useState([]);
  const [isProcessing, setIsProcessing] = useState(false);
  
  const processBatch = async () => {
    setIsProcessing(true);
    
    // Process items in batches after interactions
    const batchSize = 10;
    const batches = [];
    
    for (let i = 0; i < items.length; i += batchSize) {
      batches.push(items.slice(i, i + batchSize));
    }
    
    for (const batch of batches) {
      await new Promise(resolve => {
        InteractionManager.runAfterInteractions(() => {
          const processed = batch.map(processItem);
          setProcessedItems(prev => [...prev, ...processed]);
          resolve();
        });
      });
    }
    
    setIsProcessing(false);
  };
  
  return (
    <View>
      <Button 
        title="Process Items" 
        onPress={processBatch}
        disabled={isProcessing}
      />
      
      <FlatList
        data={processedItems}
        renderItem={({item}) => <ProcessedItem item={item} />}
      />
      
      {isProcessing && (
        <ActivityIndicator style={styles.processingIndicator} />
      )}
    </View>
  );
}
interface InteractionManagerStatic {
  // Run after interactions
  runAfterInteractions(callback: () => void | Promise<any>): {
    then: (callback: () => void) => {cancel: () => void};
    done: (...args: any[]) => any;
    cancel: () => void;
  };
  
  // Manual interaction handles
  createInteractionHandle(): number;
  clearInteractionHandle(handle: number): void;
  
  // Event listeners
  addListener?(callback: () => void): void;
  
  // Promise-based API
  setDeadline?(deadline: number): void;
}

interface InteractionHandle {
  then(callback: () => void): {cancel: () => void};
  done(...args: any[]): any;
  cancel(): void;
}

This comprehensive animation and interaction documentation provides developers with all the tools needed to create smooth, engaging user experiences with React Native's powerful animation APIs and gesture handling capabilities.

Install with Tessl CLI

npx tessl i tessl/npm-react-native@1000.0.0

docs

animation.md

core-components.md

index.md

native-bridge.md

platform-apis.md

react-hooks.md

styling.md

user-interaction.md

tile.json