CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-native-reanimated

More powerful alternative to Animated library for React Native with UI thread animations and advanced gesture handling.

Pending
Overview
Eval results
Files

event-handling.mddocs/

Event Handling

Optimized handlers for user interactions, scrolling, device events, and other reactive patterns that run efficiently on the UI thread.

Capabilities

Scroll Event Handling

High-performance scroll event handlers that can update shared values on the UI thread.

/**
 * Creates an optimized scroll event handler that runs on the UI thread
 * @param handler - Scroll handler function or configuration object
 * @returns Processed scroll handler for use with ScrollView components
 */
function useAnimatedScrollHandler<T>(
  handler: ScrollHandler<T> | ScrollHandlers<T>
): ScrollHandlerProcessed<T>;

type ScrollHandler<T> = (event: ScrollEvent, context?: T) => void;

interface ScrollHandlers<T> {
  onScroll?: ScrollHandler<T>;
  onBeginDrag?: ScrollHandler<T>;
  onEndDrag?: ScrollHandler<T>;
  onMomentumBegin?: ScrollHandler<T>;
  onMomentumEnd?: ScrollHandler<T>;
}

interface ScrollEvent {
  contentOffset: { x: number; y: number };
  contentSize: { width: number; height: number };
  layoutMeasurement: { width: number; height: number };
  targetContentOffset?: { x: number; y: number };
  velocity?: { x: number; y: number };
  zoomScale?: number;
}

Usage Examples:

import React from "react";
import Animated, { 
  useSharedValue, 
  useAnimatedScrollHandler,
  useAnimatedStyle,
  interpolate,
  Extrapolation 
} from "react-native-reanimated";

const ScrollHandlerExample = () => {
  const scrollY = useSharedValue(0);
  const headerHeight = 200;

  // Simple scroll handler
  const scrollHandler = useAnimatedScrollHandler({
    onScroll: (event) => {
      scrollY.value = event.contentOffset.y;
    },
  });

  // Advanced scroll handler with multiple events
  const advancedScrollHandler = useAnimatedScrollHandler({
    onScroll: (event) => {
      scrollY.value = event.contentOffset.y;
    },
    onBeginDrag: (event) => {
      console.log("Started dragging");
    },
    onEndDrag: (event) => {
      console.log("Stopped dragging");
    },
    onMomentumBegin: (event) => {
      console.log("Momentum scroll started");
    },
    onMomentumEnd: (event) => {
      console.log("Momentum scroll ended");
    },
  });

  // Parallax header style
  const headerStyle = useAnimatedStyle(() => ({
    transform: [
      {
        translateY: interpolate(
          scrollY.value,
          [0, headerHeight],
          [0, -headerHeight / 2],
          Extrapolation.CLAMP
        ),
      },
    ],
    opacity: interpolate(
      scrollY.value,
      [0, headerHeight / 2, headerHeight],
      [1, 0.5, 0],
      Extrapolation.CLAMP
    ),
  }));

  return (
    <>
      {/* Parallax header */}
      <Animated.View 
        style={[
          {
            position: "absolute",
            top: 0,
            left: 0,
            right: 0,
            height: headerHeight,
            backgroundColor: "lightblue",
            zIndex: 1,
          },
          headerStyle,
        ]}
      >
        <Animated.Text>Parallax Header</Animated.Text>
      </Animated.View>

      {/* Scrollable content */}
      <Animated.ScrollView
        onScroll={scrollHandler}
        scrollEventThrottle={16}
        style={{ paddingTop: headerHeight }}
      >
        {/* Content items */}
        {Array.from({ length: 50 }).map((_, index) => (
          <Animated.View
            key={index}
            style={{
              height: 80,
              backgroundColor: index % 2 ? "white" : "lightgray",
              justifyContent: "center",
              alignItems: "center",
            }}
          >
            <Animated.Text>Item {index}</Animated.Text>
          </Animated.View>
        ))}
      </Animated.ScrollView>
    </>
  );
};

Scroll Offset Tracking

Utilities for tracking scroll position with animated refs.

/**
 * Tracks scroll offset of a scrollable component
 * @param scrollableRef - Animated ref to the scrollable component
 * @returns SharedValue containing the current scroll offset
 */
function useScrollOffset(scrollableRef: AnimatedRef<any>): SharedValue<number>;

/**
 * @deprecated Use useScrollOffset instead
 */
function useScrollViewOffset(scrollableRef: AnimatedRef<any>): SharedValue<number>;

Usage Example:

import React, { useRef } from "react";
import Animated, { 
  useAnimatedRef,
  useScrollOffset,
  useAnimatedStyle,
  interpolateColor 
} from "react-native-reanimated";

const ScrollOffsetExample = () => {
  const scrollRef = useAnimatedRef();
  const scrollOffset = useScrollOffset(scrollRef);

  // Background color changes based on scroll position
  const backgroundStyle = useAnimatedStyle(() => ({
    backgroundColor: interpolateColor(
      scrollOffset.value,
      [0, 300, 600],
      ["white", "lightblue", "darkblue"]
    ),
  }));

  return (
    <Animated.View style={[{ flex: 1 }, backgroundStyle]}>
      <Animated.ScrollView ref={scrollRef}>
        {Array.from({ length: 100 }).map((_, index) => (
          <Animated.View
            key={index}
            style={{
              height: 60,
              margin: 10,
              backgroundColor: "rgba(255, 255, 255, 0.8)",
              borderRadius: 10,
              justifyContent: "center",
              alignItems: "center",
            }}
          >
            <Animated.Text>Item {index}</Animated.Text>
          </Animated.View>
        ))}
      </Animated.ScrollView>
    </Animated.View>
  );
};

Animated Reactions

React to changes in shared values with custom side effects.

/**
 * Executes side effects in response to shared value changes
 * @param prepare - Function to prepare/compute values (runs on UI thread)
 * @param react - Function to handle changes (runs on JS thread)
 * @param dependencies - Optional dependency array for optimization
 */
function useAnimatedReaction<T>(
  prepare: () => T,
  react: (prepared: T, previous: T | null) => void,
  dependencies?: React.DependencyList
): void;

Usage Examples:

import React, { useState } from "react";
import Animated, { 
  useSharedValue, 
  useAnimatedReaction,
  useAnimatedStyle,
  withSpring,
  runOnJS 
} from "react-native-reanimated";

const AnimatedReactionExample = () => {
  const scale = useSharedValue(1);
  const [message, setMessage] = useState("Normal size");

  // React to scale changes
  useAnimatedReaction(
    () => scale.value > 1.5,
    (isLarge, wasLarge) => {
      if (isLarge !== wasLarge) {
        runOnJS(setMessage)(isLarge ? "Large!" : "Normal size");
      }
    }
  );

  // React to multiple values
  const rotation = useSharedValue(0);
  const position = useSharedValue({ x: 0, y: 0 });
  
  useAnimatedReaction(
    () => ({
      isRotated: Math.abs(rotation.value) > 45,
      isOffCenter: Math.abs(position.value.x) > 50 || Math.abs(position.value.y) > 50,
    }),
    (current, previous) => {
      if (current.isRotated && !previous?.isRotated) {
        runOnJS(console.log)("Started rotating");
      }
      if (current.isOffCenter && !previous?.isOffCenter) {
        runOnJS(console.log)("Moved off center");
      }
    }
  );

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

  const handlePress = () => {
    scale.value = withSpring(scale.value === 1 ? 2 : 1);
    rotation.value = withSpring(rotation.value + 90);
    position.value = withSpring({
      x: Math.random() * 100 - 50,
      y: Math.random() * 100 - 50,
    });
  };

  return (
    <>
      <Animated.Text>{message}</Animated.Text>
      <Animated.View
        style={[
          {
            width: 100,
            height: 100,
            backgroundColor: "lightcoral",
            borderRadius: 10,
          },
          animatedStyle,
        ]}
      />
      <Button title="Animate" onPress={handlePress} />
    </>
  );
};

Keyboard Handling

Track keyboard animations and adjust UI accordingly.

/**
 * Provides animated keyboard information
 * @param options - Optional configuration for keyboard tracking
 * @returns Animated keyboard information object
 */
function useAnimatedKeyboard(options?: AnimatedKeyboardOptions): AnimatedKeyboardInfo;

interface AnimatedKeyboardInfo {
  /** Current keyboard height as SharedValue */
  height: SharedValue<number>;
  /** Keyboard state as SharedValue */
  state: SharedValue<KeyboardState>;
}

interface AnimatedKeyboardOptions {
  /** Whether to use safe area insets */
  isStatusBarTranslucentAndroid?: boolean;
}

enum KeyboardState {
  CLOSED = 0,
  OPEN = 1,
  CLOSING = 2,
  OPENING = 3,
}

Usage Example:

import React from "react";
import { TextInput } from "react-native";
import Animated, { 
  useAnimatedKeyboard,
  useAnimatedStyle,
  KeyboardState 
} from "react-native-reanimated";

const KeyboardExample = () => {
  const keyboard = useAnimatedKeyboard();

  // Adjust container based on keyboard
  const containerStyle = useAnimatedStyle(() => ({
    paddingBottom: keyboard.height.value,
    backgroundColor: keyboard.state.value === KeyboardState.OPEN ? "lightblue" : "white",
  }));

  // Animate input field
  const inputStyle = useAnimatedStyle(() => ({
    transform: [
      {
        translateY: keyboard.state.value === KeyboardState.OPENING ? -20 : 0,
      },
    ],
  }));

  return (
    <Animated.View style={[{ flex: 1, padding: 20 }, containerStyle]}>
      <Animated.View style={inputStyle}>
        <TextInput
          placeholder="Type something..."
          style={{
            height: 40,
            borderWidth: 1,
            borderColor: "gray",
            borderRadius: 5,
            paddingHorizontal: 10,
          }}
        />
      </Animated.View>
    </Animated.View>
  );
};

Sensor Handling

Access device sensors with animated values.

/**
 * Provides access to device sensors with animated values
 * @param sensorType - Type of sensor to access
 * @param config - Optional sensor configuration
 * @returns Animated sensor data
 */
function useAnimatedSensor(
  sensorType: SensorType,
  config?: SensorConfig
): AnimatedSensor;

enum SensorType {
  ACCELEROMETER = 1,
  GYROSCOPE = 2,
  GRAVITY = 3,
  MAGNETIC_FIELD = 4,
  ROTATION = 5,
}

interface SensorConfig {
  /** Sensor update interval in milliseconds */
  interval?: number;
  /** Whether to adjust for device orientation */
  adjustToInterfaceOrientation?: boolean;
  /** Interface orientation to adjust for */
  interfaceOrientation?: InterfaceOrientation;
  /** iOS reference frame */
  iosReferenceFrame?: IOSReferenceFrame;
}

interface AnimatedSensor {
  sensor: SharedValue<Value3D | ValueRotation>;
  unregister: () => void;
}

interface Value3D {
  x: number;
  y: number;
  z: number;
  interfaceOrientation: InterfaceOrientation;
}

interface ValueRotation {
  qw: number;
  qx: number;
  qy: number;
  qz: number;
  yaw: number;
  pitch: number;
  roll: number;
  interfaceOrientation: InterfaceOrientation;
}

Usage Example:

import React, { useEffect } from "react";
import Animated, { 
  useAnimatedSensor,
  useAnimatedStyle,
  SensorType 
} from "react-native-reanimated";

const SensorExample = () => {
  const accelerometer = useAnimatedSensor(SensorType.ACCELEROMETER, {
    interval: 100, // Update every 100ms
  });

  const gyroscope = useAnimatedSensor(SensorType.GYROSCOPE);

  // Tilt effect based on accelerometer
  const tiltStyle = useAnimatedStyle(() => {
    const { x, y } = accelerometer.sensor.value;
    return {
      transform: [
        { rotateX: `${y * 45}deg` },
        { rotateY: `${-x * 45}deg` },
      ],
    };
  });

  // Rotation based on gyroscope
  const rotationStyle = useAnimatedStyle(() => {
    const { z } = gyroscope.sensor.value;
    return {
      transform: [{ rotateZ: `${z * 180}deg` }],
    };
  });

  useEffect(() => {
    // Cleanup sensors on unmount
    return () => {
      accelerometer.unregister();
      gyroscope.unregister();
    };
  }, []);

  return (
    <>
      <Animated.Text>Tilt your device!</Animated.Text>
      
      <Animated.View
        style={[
          {
            width: 100,
            height: 100,
            backgroundColor: "lightgreen",
            margin: 20,
            borderRadius: 10,
          },
          tiltStyle,
        ]}
      >
        <Animated.Text>Tilt</Animated.Text>
      </Animated.View>

      <Animated.View
        style={[
          {
            width: 100,
            height: 100,
            backgroundColor: "lightcoral",
            margin: 20,
            borderRadius: 10,
          },
          rotationStyle,
        ]}
      >
        <Animated.Text>Rotate</Animated.Text>
      </Animated.View>
    </>
  );
};

Generic Event Handling

Handle custom events and gestures with optimized performance.

/**
 * Creates a generic event handler that can run on the UI thread
 * @param handler - Event handler function
 * @param eventNames - Optional array of event names to handle
 * @returns Processed event handler
 */
function useEvent<T>(
  handler: EventHandler<T>,
  eventNames?: string[]
): EventHandlerProcessed<T>;

type EventHandler<T> = (event: ReanimatedEvent<T>) => void;

interface ReanimatedEvent<T> {
  eventName: string;
  [key: string]: any;
}

Accessibility Support

Check for reduced motion preferences for accessibility.

/**
 * Returns the current reduced motion preference
 * @returns Boolean indicating if reduced motion is preferred
 */
function useReducedMotion(): SharedValue<boolean>;

Usage Example:

import React from "react";
import Animated, { 
  useReducedMotion,
  useSharedValue,
  useAnimatedStyle,
  withTiming,
  withSpring 
} from "react-native-reanimated";
import { Button } from "react-native";

const AccessibilityExample = () => {
  const reducedMotion = useReducedMotion();
  const scale = useSharedValue(1);

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

  const handlePress = () => {
    // Use different animation based on accessibility preference
    if (reducedMotion.value) {
      // Reduced motion: quick, simple animation
      scale.value = withTiming(scale.value === 1 ? 1.1 : 1, { duration: 200 });
    } else {
      // Full motion: bouncy spring animation
      scale.value = withSpring(scale.value === 1 ? 1.3 : 1);
    }
  };

  return (
    <>
      <Animated.View
        style={[
          {
            width: 100,
            height: 100,
            backgroundColor: "lightblue",
            borderRadius: 10,
          },
          animatedStyle,
        ]}
      >
        <Animated.Text>Accessible Animation</Animated.Text>
      </Animated.View>
      
      <Button title="Animate" onPress={handlePress} />
    </>
  );
};

Advanced Event Handling

Low-level event handling hooks for complex event composition and custom patterns.

/**
 * Creates a generic event handler for any native event
 * @param handler - Function to handle the event
 * @param eventNames - Array of event names to listen for
 * @param rebuild - Whether to rebuild the handler on changes
 * @returns Processed event handler
 */
function useEvent<Event extends object, Context = never>(
  handler: EventHandler<Event, Context>,
  eventNames?: readonly string[],
  rebuild?: boolean
): EventHandlerProcessed<Event, Context>;

/**
 * Composes multiple event handlers into a single handler
 * @param handlers - Array of event handlers to compose
 * @returns Composed event handler
 */
function useComposedEventHandler<Event extends object, Context = never>(
  handlers: (EventHandlerProcessed<Event, Context> | null)[]
): EventHandlerProcessed<Event, Context>;

/**
 * Low-level hook for creating reusable event handler logic
 * @param handlers - Object of event handlers
 * @param dependencies - Optional dependency array
 * @returns Handler context with dependency information
 */
function useHandler<Event extends object, Context extends Record<string, unknown>>(
  handlers: Record<string, ((event: ReanimatedEvent<Event>, context: Context) => void) | undefined>,
  dependencies?: React.DependencyList
): UseHandlerContext<Context>;

type EventHandler<Event extends object, Context = never> = 
  (event: ReanimatedEvent<Event>, context?: Context) => void;

Usage Examples:

// Generic event handling
const MyComponent = () => {
  const handleTouch = useEvent((event) => {
    'worklet';
    console.log('Touch at:', event.x, event.y);
  }, ['onTouchStart', 'onTouchMove']);

  return <Animated.View onTouchStart={handleTouch} onTouchMove={handleTouch} />;
};

// Composing multiple handlers
const ComposedExample = () => {
  const handler1 = useEvent((event) => {
    'worklet';
    console.log('Handler 1:', event.contentOffset.y);
  });

  const handler2 = useEvent((event) => {
    'worklet';
    console.log('Handler 2:', event.velocity?.y);
  });

  const composedHandler = useComposedEventHandler([handler1, handler2]);

  return <Animated.ScrollView onScroll={composedHandler} />;
};

// Advanced handler patterns
const AdvancedExample = () => {
  const { context, doDependenciesDiffer } = useHandler({
    onScroll: (event, ctx) => {
      'worklet';
      ctx.lastScrollY = event.contentOffset.y;
    },
    onMomentumEnd: (event, ctx) => {
      'worklet';
      console.log('Scroll ended at:', ctx.lastScrollY);
    },
  }, []);

  // Use the handler context for complex logic
  useEffect(() => {
    if (doDependenciesDiffer) {
      console.log('Handler dependencies changed');
    }
  }, [doDependenciesDiffer]);

  return <Animated.ScrollView /* handler setup */ />;
};

Type Definitions

type ScrollHandlerProcessed<T> = (event: ScrollEvent) => void;

type EventHandlerProcessed<T> = (event: ReanimatedEvent<T>) => void;

interface UseHandlerContext<T> {
  context: SharedValue<T>;
  doDependenciesDiffer: boolean;
  useWeb: boolean;
}

enum InterfaceOrientation {
  ROTATION_0 = 0,
  ROTATION_90 = 90,
  ROTATION_180 = 180,
  ROTATION_270 = 270,
}

enum IOSReferenceFrame {
  XArbitraryZVertical = "XArbitraryZVertical",
  XArbitraryCorrectedZVertical = "XArbitraryCorrectedZVertical",
  XMagneticNorthZVertical = "XMagneticNorthZVertical",
  XTrueNorthZVertical = "XTrueNorthZVertical",
}

Install with Tessl CLI

npx tessl i tessl/npm-react-native-reanimated

docs

animated-components.md

animation-functions.md

configuration-utilities.md

core-reactive-system.md

css-integration.md

event-handling.md

index.md

interpolation-easing.md

layout-animations.md

platform-functions.md

screen-transitions.md

testing-utilities.md

worklet-functions.md

tile.json