-
So this is my parent component as an animated view:
and this is my animated background
However, whenever the exiting fadeout animation is played, the background animation is stopped and just pauses in place, but i want it to continue playing throughout the exiting fade animation. Any ideas on how to fix this? |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
Hey! The issue you face is caused by the fact how layout animations work. Technically, the component that will be unmounted no longer receives updates from RN, so animated style no longer applies transformation to your background component. The component you see on the screen during the animation is a snapshot of the component that was rendered on the screen just before it is removed from the component tree. Solution: Custom exiting animationThere is a workaround in which you have to implement a custom You can apply a custom // Moved interpolate to a separate function to easier calculate transformation in multiple places
const interpolateAnimation = (progress: number) => {
'worklet';
return interpolate(
progress,
[INPUT_RANGE_START, INPUT_RANGE_END],
[OUTPUT_RANGE_START, OUTPUT_RANGE_END]
);
};
const exiting = (progress: SharedValue<number>, duration: number) => () => {
'worklet';
// Calculate the current transformation (initialTransform) and the resulting after
// `duration` ms
const initialTransform = interpolateAnimation(progress.value);
const targetTransform = interpolateAnimation(
progress.value +
((ANIMATION_TO_VALUE - ANIMATION_START_VALUE) * duration) /
ANIMATION_DURATION
);
const animate = () =>
withTiming(targetTransform, {
duration,
easing: Easing.linear,
});
const animations = {
// Define the same transform animations as in the animated style
transform: [
{
translateX: animate(),
},
{
translateY: animate(),
},
],
};
// Start from the transformation that was applied when the background component got unmounted
const initialValues = {
transform: [
{
translateX: initialTransform,
},
{
translateY: initialTransform,
},
],
};
return {
initialValues,
animations,
};
}; Apply the animation to the <AnimatedImage
exiting={exiting(translationAnimation, EXITING_DURATION)}
...
/> Complete codeimport { useEffect, useState } from 'react';
import { ImageBackground, Image, StyleSheet, Button, View } from 'react-native';
import type { SharedValue } from 'react-native-reanimated';
import Animated, {
Easing,
FadeOut,
interpolate,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
export const INPUT_RANGE_START = 0;
export const INPUT_RANGE_END = 1;
export const OUTPUT_RANGE_START = -281;
export const OUTPUT_RANGE_END = 0;
const ANIMATION_START_VALUE = 0;
export const ANIMATION_TO_VALUE = 1;
export const ANIMATION_DURATION = 25000;
const EXITING_DURATION = 1000;
export default function App() {
const [hide, setHide] = useState(false);
return (
<>
<View style={styles.button}>
<Button title={hide ? 'Show' : 'Hide'} onPress={() => setHide(!hide)} />
</View>
{!hide && (
<>
<Animated.View
style={[StyleSheet.absoluteFill, { zIndex: 1 }]}
exiting={FadeOut.duration(EXITING_DURATION)}>
<AnimatedBackground />
<Image
source={require('./assets/splash.png')}
style={{ width: '100%', height: '100%' }}
resizeMode="contain"
/>
</Animated.View>
</>
)}
</>
);
}
const AnimatedImage = Animated.createAnimatedComponent(ImageBackground);
const interpolateAnimation = (progress: number) => {
'worklet';
return interpolate(
progress,
[INPUT_RANGE_START, INPUT_RANGE_END],
[OUTPUT_RANGE_START, OUTPUT_RANGE_END]
);
};
const exiting = (progress: SharedValue<number>, duration: number) => () => {
'worklet';
const initialTransform = interpolateAnimation(progress.value);
const targetTransform = interpolateAnimation(
progress.value +
((ANIMATION_TO_VALUE - ANIMATION_START_VALUE) * duration) /
ANIMATION_DURATION
);
const animate = () =>
withTiming(targetTransform, {
duration,
easing: Easing.linear,
});
const animations = {
transform: [
{
translateX: animate(),
},
{
translateY: animate(),
},
],
};
const initialValues = {
transform: [
{
translateX: initialTransform,
},
{
translateY: initialTransform,
},
],
};
return {
initialValues,
animations,
};
};
function AnimatedBackground() {
const translationAnimation = useSharedValue(ANIMATION_START_VALUE);
const translationStyle = useAnimatedStyle(() => {
const animation = interpolateAnimation(translationAnimation.value);
return {
transform: [
{
translateX: animation,
},
{
translateY: animation,
},
],
};
});
useEffect(() => {
translationAnimation.value = ANIMATION_START_VALUE;
translationAnimation.value = withTiming(ANIMATION_TO_VALUE, {
duration: ANIMATION_DURATION,
easing: Easing.linear,
});
}, [translationAnimation]);
return (
<AnimatedImage
exiting={exiting(translationAnimation, EXITING_DURATION)}
resizeMode={'repeat'}
style={[styles.background, translationStyle]}
source={require('./assets/background.png')}
/>
);
}
const styles = StyleSheet.create({
background: {
position: 'absolute',
width: 1200,
height: 1200,
top: 0,
opacity: 0.2,
},
button: {
position: 'absolute',
justifyContent: 'center',
width: '100%',
zIndex: 2,
},
}); |
Beta Was this translation helpful? Give feedback.
Hey!
The issue you face is caused by the fact how layout animations work. Technically, the component that will be unmounted no longer receives updates from RN, so animated style no longer applies transformation to your background component. The component you see on the screen during the animation is a snapshot of the component that was rendered on the screen just before it is removed from the component tree.
Solution: Custom exiting animation
There is a workaround in which you have to implement a custom
exiting
layout animation, which, in fact, will continue your animation from the animated style (see docs for more details).You can apply a custom
exiting
animation to theAnimatedImage
in…