Last active
May 12, 2025 14:42
-
-
Save marcelosanchez/d3d7b2cd0c8b5eb9714fdcd986e9f9ba to your computer and use it in GitHub Desktop.
React Native Reanimated: Hook to apply custom pivot rotation transform
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useSharedValue, useAnimatedStyle, withTiming, useDerivedValue, Easing } from 'react-native-reanimated'; | |
// Matrix functions | |
const createTranslationMatrix = (dx: number, dy: number): number[] => { | |
'worklet'; | |
return [1, 0, 0, 0, 1, 0, dx, dy, 1]; | |
}; | |
const createRotationMatrix = (radians: number): number[] => { | |
'worklet'; | |
const cos = Math.cos(radians); | |
const sin = Math.sin(radians); | |
return [cos, -sin, 0, sin, cos, 0, 0, 0, 1]; | |
}; | |
const multiplyMatrices = (A: number[], B: number[]): number[] => { | |
'worklet'; | |
const C = new Array(9).fill(0); | |
for (let i = 0; i < 3; i++) { | |
for (let j = 0; j < 3; j++) { | |
for (let k = 0; k < 3; k++) { | |
C[i * 3 + j] += A[i * 3 + k] * B[k * 3 + j]; | |
} | |
} | |
} | |
return C; | |
}; | |
const invertMatrix = (m: number[]): number[] => { | |
'worklet'; | |
const det = m[0] * (m[4] * m[8] - m[5] * m[7]) | |
- m[1] * (m[3] * m[8] - m[5] * m[6]) | |
+ m[2] * (m[3] * m[7] - m[4] * m[6]); | |
if (det === 0) { | |
console.warn('invertMatrix: The matrix is not invertible (det = 0). Returning original matrix'); | |
return [...m]; | |
} | |
const invDet = 1 / det; | |
return [ | |
(m[4] * m[8] - m[5] * m[7]) * invDet, | |
(m[2] * m[7] - m[1] * m[8]) * invDet, | |
(m[1] * m[5] - m[2] * m[4]) * invDet, | |
(m[5] * m[6] - m[3] * m[8]) * invDet, | |
(m[0] * m[8] - m[2] * m[6]) * invDet, | |
(m[2] * m[3] - m[0] * m[5]) * invDet, | |
(m[3] * m[7] - m[4] * m[6]) * invDet, | |
(m[1] * m[6] - m[0] * m[7]) * invDet, | |
(m[0] * m[4] - m[1] * m[3]) * invDet, | |
]; | |
}; | |
const applyMatrix = (m: number[], x: number, y: number): { x: number; y: number } => { | |
'worklet'; | |
return { | |
x: m[0] * x + m[1] * y + m[2] * 1, | |
y: m[3] * x + m[4] * y + m[5] * 1, | |
}; | |
}; | |
// Custom hook for pivot transformation | |
const usePivotTransform = ( | |
width: number, | |
height: number, | |
initialPivotX: number, | |
initialPivotY: number, | |
centerX: number, | |
centerY: number, | |
) => { | |
const rotation = useSharedValue(0); | |
const boxX = useSharedValue(centerX); | |
const boxY = useSharedValue(centerY); | |
const pivotX = useSharedValue(initialPivotX); | |
const pivotY = useSharedValue(initialPivotY); | |
// Estilo animado para el View | |
const animatedStyle = useAnimatedStyle(() => { | |
'worklet'; | |
const boxXVal = boxX.value; | |
const boxYVal = boxY.value; | |
const pivotXVal = pivotX.value; | |
const pivotYVal = pivotY.value; | |
const rotationVal = rotation.value; | |
const rotate = `${rotationVal}deg`; | |
return { | |
transform: [ | |
{ translateX: boxXVal - width / 2 }, | |
{ translateY: boxYVal - height / 2 }, | |
{ translateX: pivotXVal - boxXVal }, | |
{ translateY: pivotYVal - boxYVal }, | |
{ rotate }, | |
{ translateX: -(pivotXVal - boxXVal) }, | |
{ translateY: -(pivotYVal - boxYVal) }, | |
], | |
}; | |
}); | |
const pivotLocal = useDerivedValue(() => { | |
'worklet'; | |
const rotationVal = rotation.value; | |
const pivotXVal = pivotX.value; | |
const pivotYVal = pivotY.value; | |
const boxXVal = boxX.value; | |
const boxYVal = boxY.value; | |
const rotationRadians = (rotationVal * Math.PI) / 180; | |
const T1 = createTranslationMatrix(boxXVal - width / 2, boxYVal - height / 2); | |
const T2 = createTranslationMatrix(pivotXVal - boxXVal, pivotYVal - boxYVal); | |
const R = createRotationMatrix(rotationRadians); | |
const T3 = createTranslationMatrix(-(pivotXVal - boxXVal), -(pivotYVal - boxYVal)); | |
let M = multiplyMatrices(T1, T2); | |
M = multiplyMatrices(M, R); | |
M = multiplyMatrices(M, T3); | |
const invM = invertMatrix(M); | |
return applyMatrix(invM, pivotXVal, pivotYVal); | |
}); | |
const pivotMarkerStyle = useAnimatedStyle(() => { | |
'worklet'; | |
const pivotLocalValue = pivotLocal.value; | |
return { | |
position: 'absolute', | |
left: pivotLocalValue.x, | |
top: pivotLocalValue.y, | |
}; | |
}); | |
const setRotation = (newRotation: number, duration: number = 1000) => { | |
rotation.value = withTiming(newRotation, { duration, easing: Easing.inOut(Easing.ease) }); | |
}; | |
const move = (dx: number, dy: number, duration: number = 1000) => { | |
boxX.value = withTiming(boxX.value + dx, { duration }); | |
boxY.value = withTiming(boxY.value + dy, { duration }); | |
}; | |
const updatePivot = (newPivotX: number, newPivotY: number) => { | |
pivotX.value = newPivotX; | |
pivotY.value = newPivotY; | |
}; | |
return { | |
rotation, | |
animatedStyle, | |
pivotMarkerStyle, | |
pivotLocal, | |
rotate: setRotation, | |
move, | |
updatePivot, | |
}; | |
}; | |
export default usePivotTransform; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example usage of
usePivotTransform
hookThis example demonstrates how to use the hook to rotate a
View
around a custom pivot point using Reanimated 2+.