UNPKG

react-native-reanimated

Version:

More powerful alternative to Animated library for React Native.

124 lines (106 loc) 3.87 kB
'use strict'; import type { AnimationData, AnimationStyle, TransitionData, } from '../animationParser'; import { AnimationsData } from '../config'; const ExitingFinalStep = 49; const EnteringStartStep = 50; type TransformData = { translateX: string; translateY: string; scale: string; }; // Layout transitions on web work in "reverse order". It means that the element is rendered at its destination and then, at the beginning of the animation, // we move it back to its starting point. // This function is responsible for adding transition data into beginning of each keyframe step. // Doing so will ensure that the element will perform animation from correct position. function addTransformToKeepPosition( keyframeStyleData: Record<number, AnimationStyle>, animationStyle: Record<number, AnimationStyle>, transformData: TransformData, isExiting: boolean ) { for (const [timestamp, styles] of Object.entries(animationStyle)) { if (styles.transform !== undefined) { // If transform was defined, we want to put transform from transition at the beginning, hence we use `unshift` styles.transform.unshift(transformData); } else { // If transform was undefined, we simply add transform from transition styles.transform = [transformData]; } const newTimestamp = parseInt(timestamp) / 2; const index = isExiting ? Math.min(newTimestamp, ExitingFinalStep) // We want to squeeze exiting animation from range 0-100 into range 0-49 : newTimestamp + EnteringStartStep; // Entering animation will start from 50 and go up to 100 keyframeStyleData[`${index}`] = styles; } } // EntryExit transition consists of two animations - exiting and entering. // In Keyframes one cannot simply specify animation for given frame. Switching from one animation // to the other one between steps 49 and 50 may lead to flickers, since browser tries to interpolate // one step into the other. To avoid that, we set components' `opacity` to 0 right before switching animation // and set it again to 1 when component is in right position. Hiding component between animations // prevents flickers. function hideComponentBetweenAnimations( keyframeStyleData: Record<number, AnimationStyle> ) { // We have to take into account that some animations have already defined `opacity`. // In that case, we don't want to override it. const opacityInStep = new Map<number, number>(); if (keyframeStyleData[0].opacity === undefined) { opacityInStep.set(48, 1); opacityInStep.set(49, 0); } if (keyframeStyleData[50].opacity === undefined) { opacityInStep.set(50, 0); opacityInStep.set(51, 1); } for (const [step, opacity] of opacityInStep) { keyframeStyleData[step] = { ...keyframeStyleData[step], opacity, }; } } export function EntryExitTransition( name: string, transitionData: TransitionData ) { const exitingAnimationData = structuredClone( AnimationsData[transitionData.exiting] ); const enteringAnimationData = structuredClone( AnimationsData[transitionData.entering] ); const additionalExitingData: TransformData = { translateX: `${transitionData.translateX}px`, translateY: `${transitionData.translateY}px`, scale: `${transitionData.scaleX},${transitionData.scaleY}`, }; const additionalEnteringData: TransformData = { translateX: `0px`, translateY: `0px`, scale: `1,1`, }; const keyframeData: AnimationData = { name, style: {}, duration: 300, }; addTransformToKeepPosition( keyframeData.style, exitingAnimationData.style, additionalExitingData, true ); addTransformToKeepPosition( keyframeData.style, enteringAnimationData.style, additionalEnteringData, false ); hideComponentBetweenAnimations(keyframeData.style); return keyframeData; }