Utility library for React Native Reanimated and Gesture Handler providing mathematical functions, animations, transformations, and helper utilities for building complex gesture-driven animations.
—
Transform utilities for applying transformations with custom origins and creating animated styles with translation effects.
import type { TransformsStyle } from "react-native";/**
* React Native transform array type
*/
type RNTransform = Exclude<TransformsStyle["transform"], undefined>;Apply transformations around a custom pivot point instead of the default center.
/**
* Apply transformations with custom origin point
* @param origin - Origin point for transformations
* @param transformations - Array of React Native transform objects
* @returns Transform array with origin translations
*/
function transformOrigin(
origin: Vector,
transformations: RNTransform
): RNTransform;
/**
* Apply 2D transformations with custom origin point
* @param origin - Origin point for transformations
* @param transformations - Array of 2D transform objects
* @returns Transform array with origin translations
*/
function transformOrigin2d(
origin: Vector,
transformations: Transforms2d
): Transforms2d;Usage Example:
import { transformOrigin, vec2 } from "react-native-redash";
import { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
export const CustomOriginRotation = () => {
const rotation = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => {
// Rotate around top-left corner instead of center
const topLeft = vec2(0, 0);
const transform = transformOrigin(topLeft, [
{ rotate: `${rotation.value}deg` },
{ scale: 1.2 }
]);
return { transform };
});
return (
<Animated.View
style={[
{ width: 100, height: 100, backgroundColor: 'blue' },
animatedStyle
]}
/>
);
};Convenient hook for creating translation-based animated styles.
/**
* Hook for translation animations using vector of SharedValues
* @param vector - Vector with SharedValue components for x and y translation
* @returns Animated style object with translation transform
*/
function useTranslation(vector: Vector<Animated.SharedValue<number>>): {
transform: { translateX: number; translateY: number }[];
};Usage Example:
import { useTranslation, useVector } from "react-native-redash";
import { useAnimatedGestureHandler } from "react-native-reanimated";
import { PanGestureHandler } from "react-native-gesture-handler";
export const DraggableElement = () => {
const position = useVector(0, 0);
const offset = useVector(0, 0);
// Use the translation hook for convenient styling
const translationStyle = useTranslation(position);
const gestureHandler = useAnimatedGestureHandler({
onStart: () => {
offset.x.value = position.x.value;
offset.y.value = position.y.value;
},
onActive: (event) => {
position.x.value = offset.x.value + event.translationX;
position.y.value = offset.y.value + event.translationY;
}
});
return (
<PanGestureHandler onGestureEvent={gestureHandler}>
<Animated.View
style={[
{ width: 100, height: 100, backgroundColor: 'red' },
translationStyle
]}
/>
</PanGestureHandler>
);
};import { transformOrigin, transformOrigin2d, vec2 } from "react-native-redash";
import { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
export const AdvancedTransforms = () => {
const rotation = useSharedValue(0);
const scale = useSharedValue(1);
// Rotate around bottom-right corner
const bottomRightRotation = useAnimatedStyle(() => {
const bottomRight = vec2(100, 100); // Assuming 100x100 element
return {
transform: transformOrigin(bottomRight, [
{ rotate: `${rotation.value}deg` }
])
};
});
// Scale from top-center
const topCenterScale = useAnimatedStyle(() => {
const topCenter = vec2(50, 0); // Center horizontally, top vertically
return {
transform: transformOrigin(topCenter, [
{ scale: scale.value }
])
};
});
// Combined transformations with custom origin
const combinedTransform = useAnimatedStyle(() => {
const customOrigin = vec2(75, 25); // 3/4 right, 1/4 down
return {
transform: transformOrigin(customOrigin, [
{ rotate: `${rotation.value}deg` },
{ scale: scale.value },
{ skewX: `${rotation.value * 0.1}deg` }
])
};
});
// Using 2D transform types
const matrix2dTransform = useAnimatedStyle(() => {
const origin = vec2(50, 50);
const transforms = transformOrigin2d(origin, [
{ rotateZ: `${rotation.value}deg` },
{ scaleX: scale.value },
{ scaleY: scale.value * 0.8 }
]);
return { transform: transforms };
});
return (
<View style={{ padding: 20 }}>
<Animated.View style={[styles.box, bottomRightRotation]} />
<Animated.View style={[styles.box, topCenterScale]} />
<Animated.View style={[styles.box, combinedTransform]} />
<Animated.View style={[styles.box, matrix2dTransform]} />
</View>
);
};import React from "react";
import { View, StyleSheet } from "react-native";
import { transformOrigin, useVector, vec2 } from "react-native-redash";
import {
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue
} from "react-native-reanimated";
import { PanGestureHandler, RotationGestureHandler } from "react-native-gesture-handler";
export const InteractiveTransformOrigin = () => {
const rotation = useSharedValue(0);
const originPosition = useVector(50, 50); // Center of 100x100 element
const elementPosition = useVector(100, 100);
// Drag to change transform origin
const originGestureHandler = useAnimatedGestureHandler({
onActive: (event) => {
originPosition.x.value = event.x;
originPosition.y.value = event.y;
}
});
// Rotate the element
const rotationGestureHandler = useAnimatedGestureHandler({
onActive: (event) => {
rotation.value = event.rotation;
}
});
// Element style with custom origin
const elementStyle = useAnimatedStyle(() => {
const origin = vec2(originPosition.x.value, originPosition.y.value);
return {
transform: transformOrigin(origin, [
{ rotate: `${rotation.value}rad` }
])
};
});
// Origin indicator style
const originStyle = useAnimatedStyle(() => ({
transform: [
{ translateX: originPosition.x.value - 5 },
{ translateY: originPosition.y.value - 5 }
]
}));
return (
<View style={styles.container}>
{/* Transform origin indicator */}
<PanGestureHandler onGestureEvent={originGestureHandler}>
<Animated.View style={[styles.origin, originStyle]} />
</PanGestureHandler>
{/* Rotatable element */}
<RotationGestureHandler onGestureEvent={rotationGestureHandler}>
<Animated.View style={[styles.element, elementStyle]} />
</RotationGestureHandler>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f0f0f0'
},
element: {
position: 'absolute',
left: 100,
top: 100,
width: 100,
height: 100,
backgroundColor: 'blue',
borderRadius: 10
},
origin: {
position: 'absolute',
width: 10,
height: 10,
borderRadius: 5,
backgroundColor: 'red',
borderWidth: 1,
borderColor: 'white'
}
});import { transformOrigin, vec2 } from "react-native-redash";
// 1. Clock hands rotating from center-bottom
const clockHandStyle = useAnimatedStyle(() => {
const handOrigin = vec2(2, 100); // 2px from left (hand width/2), 100px from top (hand length)
return {
transform: transformOrigin(handOrigin, [
{ rotate: `${hourAngle.value}deg` }
])
};
});
// 2. Card flipping from left edge
const cardFlipStyle = useAnimatedStyle(() => {
const leftEdge = vec2(0, cardHeight / 2);
return {
transform: transformOrigin(leftEdge, [
{ rotateY: `${flipAngle.value}deg` }
])
};
});
// 3. Door opening from hinges
const doorStyle = useAnimatedStyle(() => {
const hingePoint = vec2(0, doorHeight / 2);
return {
transform: transformOrigin(hingePoint, [
{ rotateZ: `${doorAngle.value}deg` }
])
};
});
// 4. Scaling from bottom (like growing plants)
const growthStyle = useAnimatedStyle(() => {
const bottomCenter = vec2(elementWidth / 2, elementHeight);
return {
transform: transformOrigin(bottomCenter, [
{ scaleY: growthScale.value }
])
};
});Important Notes:
useTranslation is a convenience hook that's equivalent to manually creating translation transformsInstall with Tessl CLI
npx tessl i tessl/npm-react-native-redash