UNPKG

react-native-reanimated

Version:

More powerful alternative to Animated library for React Native.

239 lines (229 loc) • 7.57 kB
'use strict'; import type { LockAxis, PanGestureHandlerEventPayload, ScreenTransitionConfig, } from './commonTypes'; import { RNScreensTurboModule } from './RNScreensTurboModule'; import { applyStyle, applyStyleForBelowTopScreen } from './styleUpdater'; const BASE_VELOCITY = 400; const ADDITIONAL_VELOCITY_FACTOR_X = 400; const ADDITIONAL_VELOCITY_FACTOR_Y = 500; const ADDITIONAL_VELOCITY_FACTOR_XY = 600; function computeEasingProgress( startingTimestamp: number, distance: number, velocity: number ) { 'worklet'; if (Math.abs(distance) < 1) { return 1; } const elapsedTime = (_getAnimationTimestamp() - startingTimestamp) / 1000; const currentPosition = velocity * elapsedTime; const progress = currentPosition / distance; return progress; } function easing(x: number): number { 'worklet'; // based on https://easings.net/#easeOutQuart return 1 - Math.pow(1 - x, 5); } function computeProgress( screenTransitionConfig: ScreenTransitionConfig, event: PanGestureHandlerEventPayload, isTransitionCanceled: boolean ) { 'worklet'; const screenDimensions = screenTransitionConfig.screenDimensions; const progressX = Math.abs(event.translationX / screenDimensions.width); const progressY = Math.abs(event.translationY / screenDimensions.height); const maxProgress = Math.max(progressX, progressY); const progress = isTransitionCanceled ? maxProgress / 2 : maxProgress; return progress; } function maybeScheduleNextFrame( step: () => void, didScreenReachDestination: boolean, screenTransitionConfig: ScreenTransitionConfig, event: PanGestureHandlerEventPayload, isTransitionCanceled: boolean ) { 'worklet'; if (!didScreenReachDestination) { const stackTag = screenTransitionConfig.stackTag; const progress = computeProgress( screenTransitionConfig, event, isTransitionCanceled ); RNScreensTurboModule.updateTransition(stackTag, progress); requestAnimationFrame(step); } else { screenTransitionConfig.onFinishAnimation?.(); } } export function getSwipeSimulator( event: PanGestureHandlerEventPayload, screenTransitionConfig: ScreenTransitionConfig, lockAxis?: LockAxis ) { 'worklet'; const screenDimensions = screenTransitionConfig.screenDimensions; const startTimestamp = _getAnimationTimestamp(); const { isTransitionCanceled } = screenTransitionConfig; const startingPosition = { x: event.translationX, y: event.translationY, }; const direction = { x: Math.sign(event.translationX), y: Math.sign(event.translationY), }; const finalPosition = isTransitionCanceled ? { x: 0, y: 0 } : { x: direction.x * screenDimensions.width, y: direction.y * screenDimensions.height, }; const distance = { x: Math.abs(finalPosition.x - startingPosition.x), y: Math.abs(finalPosition.y - startingPosition.y), }; const didScreenReachDestination = { x: false, y: false, }; const velocity = { x: BASE_VELOCITY, y: BASE_VELOCITY }; if (lockAxis === 'x') { velocity.y = 0; velocity.x += (ADDITIONAL_VELOCITY_FACTOR_X * distance.x) / screenDimensions.width; } else if (lockAxis === 'y') { velocity.x = 0; velocity.y += (ADDITIONAL_VELOCITY_FACTOR_Y * distance.y) / screenDimensions.height; } else { const euclideanDistance = Math.sqrt(distance.x ** 2 + distance.y ** 2); const screenDiagonal = Math.sqrt( screenDimensions.width ** 2 + screenDimensions.height ** 2 ); const velocityVectorLength = BASE_VELOCITY + (ADDITIONAL_VELOCITY_FACTOR_XY * euclideanDistance) / screenDiagonal; if (Math.abs(startingPosition.x) > Math.abs(startingPosition.y)) { velocity.x = velocityVectorLength; velocity.y = velocityVectorLength * Math.abs(startingPosition.y / startingPosition.x); } else { velocity.x = velocityVectorLength * Math.abs(startingPosition.x / startingPosition.y); velocity.y = velocityVectorLength; } } if (isTransitionCanceled) { function didScreenReachDestinationCheck() { if (lockAxis === 'x') { return didScreenReachDestination.x; } else if (lockAxis === 'y') { return didScreenReachDestination.y; } else { return didScreenReachDestination.x && didScreenReachDestination.y; } } function restoreOriginalStyleForBelowTopScreen() { event.translationX = direction.x * screenDimensions.width; event.translationY = direction.y * screenDimensions.height; applyStyleForBelowTopScreen(screenTransitionConfig, event); } const computeFrame = () => { const progress = { x: computeEasingProgress(startTimestamp, distance.x, velocity.x), y: computeEasingProgress(startTimestamp, distance.y, velocity.y), }; event.translationX = startingPosition.x - direction.x * distance.x * easing(progress.x); event.translationY = startingPosition.y - direction.y * distance.y * easing(progress.y); if (direction.x > 0) { if (event.translationX <= 0) { didScreenReachDestination.x = true; event.translationX = 0; } } else { if (event.translationX >= 0) { didScreenReachDestination.x = true; event.translationX = 0; } } if (direction.y > 0) { if (event.translationY <= 0) { didScreenReachDestination.y = true; event.translationY = 0; } } else { if (event.translationY >= 0) { didScreenReachDestination.y = true; event.translationY = 0; } } applyStyle(screenTransitionConfig, event); const finished = didScreenReachDestinationCheck(); if (finished) { restoreOriginalStyleForBelowTopScreen(); } maybeScheduleNextFrame( computeFrame, finished, screenTransitionConfig, event, isTransitionCanceled ); }; return computeFrame; } else { const computeFrame = () => { const progress = { x: computeEasingProgress(startTimestamp, distance.x, velocity.x), y: computeEasingProgress(startTimestamp, distance.y, velocity.y), }; event.translationX = startingPosition.x + direction.x * distance.x * easing(progress.x); event.translationY = startingPosition.y + direction.y * distance.y * easing(progress.y); if (direction.x > 0) { if (event.translationX >= screenDimensions.width) { didScreenReachDestination.x = true; event.translationX = screenDimensions.width; } } else { if (event.translationX <= -screenDimensions.width) { didScreenReachDestination.x = true; event.translationX = -screenDimensions.width; } } if (direction.y > 0) { if (event.translationY >= screenDimensions.height) { didScreenReachDestination.y = true; event.translationY = screenDimensions.height; } } else { if (event.translationY <= -screenDimensions.height) { didScreenReachDestination.y = true; event.translationY = -screenDimensions.height; } } applyStyle(screenTransitionConfig, event); maybeScheduleNextFrame( computeFrame, didScreenReachDestination.x || didScreenReachDestination.y, screenTransitionConfig, event, isTransitionCanceled ); }; return computeFrame; } }