More powerful alternative to Animated library for React Native with UI thread animations and advanced gesture handling.
—
Optimized handlers for user interactions, scrolling, device events, and other reactive patterns that run efficiently on the UI thread.
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>
</>
);
};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>
);
};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} />
</>
);
};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>
);
};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>
</>
);
};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;
}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} />
</>
);
};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 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