Last week, the Software Mansion team released react-native-reanimated 3.0.0, which brings a lot of new exciting features and improved stability to the animation library.
Some parts of the Reanimated core codebase have been rewritten from the ground up, such as isAnimated
, the Shared Value implementation, and other performance critical parts.
Additionally, they introduced a new feature - Shared Element Transitions - and in this blogpost I'm going to go over all you need to know to use Shared Element Transitions in your app! 🚀
So.. What are Shared Element Transitions? Shared Element Transitions (or "Hero" animations) are screen navigation animations, that animate an element between two screens. Lots of apps are using these animations nowadays, for example the Instagram app implemented Shared Element Transitions for their Post View:
Without the right tools, implementing such an animation is insanely difficult, as you would need to take spacings, safe areas, screen coordinates, view width/height, border radius, opacity, image resizeMode, and many other things into account to create a smooth experience.
With Reanimated V3, things got a lot easier, so let's dive straight in!
Setting up your project
Create a new React Native (or Expo) app:
npx react-native init SharedElementExample
Then, install Reanimated:
yarn add react-native-reanimated
Make sure you installed version 3.0.2 or higher!
And lastly, install the navigation library:
yarn add @react-navigation/native # navigation base library
yarn add @react-navigation/native-stack # the stack we use
yarn add react-native-screens # required for native stack
yarn add react-native-safe-area-context # required for safe-area
Shared Element Transitions only work with the native stack navigator (react-native-screens). The native stack navigator is faster anyways, so I recommend using that in every situation.
Let's start coding!
First, create the root component and entry point for our app (App.tsx
):
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { Home } from './screens/Home'
import { CarDetails } from './screens/CarDetails'
const Stack = createNativeStackNavigator()
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="CarDetails" component={CarDetails} />
</Stack.Navigator>
</NavigationContainer>
);
}
This is the main navigator component that connects the two screens (Home
and CarDetails
) and mounts them in a Native Stack. Navigation between those screens is handled using react-navigation.
Next, implement the two screens. Let's start with Home
:
function Home({ navigation }) {
return (
<View style={{ flex: 1 }}>
<Animated.View
style={{ width: 150, height: 150, backgroundColor: 'green' }}
sharedTransitionTag="sharedTag"
/>
<Button
title="Go to Details"
onPress={() => navigation.navigate('CarDetails')}
/>
</View>
);
}
We created an Animated.View
(from react-native-reanimated), and gave it a sharedTransitionTag
. This is the tag that Reanimated uses to find out which elements to connect in a Shared Element Transition. Next, let's implement CarDetails
and give the Animated.View
the same sharedTransitionTag
:
function CarDetails({ navigation }) {
return (
<View style={{ flex: 1, marginTop: 50 }}>
<Animated.View
style={{ width: 100, height: 100, backgroundColor: 'green' }}
sharedTransitionTag="sharedTag"
/>
<Button
title="Go back"
onPress={() => navigation.navigate('Home')}
/>
</View>
);
}
Okay - let's run it! Here's how it looks:
Note: If you can't see that transition happening, make sure to clean your cache (
node_modules
,yarn.lock
,ios/Pods
) and try rebuilding your app. Also make sure you're on Reanimated V3, and that both of your views have the samesharedTransitionTag
.
Making it butter-smooth!
The animation works, but it feels a bit rough.
Let's customize the animation a bit!
You can use the sharedTransitionStyle
prop on the Animated.View
to pass a custom Reanimated animation Worklet to the Shared Element Transition. This is similar to a useAnimatedStyle
, but with target values that you need to animate to.
Let's create a helper file:
import Animated, {
SharedTransition,
withSpring,
WithSpringConfig,
} from 'react-native-reanimated'
const SPRING_CONFIG = {
mass: 1,
stiffness: 100,
damping: 200,
}
export const sharedElementTransition = SharedTransition.custom((values) => {
'worklet'
return {
height: withSpring(values.targetHeight, SPRING_CONFIG),
width: withSpring(values.targetWidth, SPRING_CONFIG),
originX: withSpring(values.targetGlobalOriginX, SPRING_CONFIG),
originY: withSpring(values.targetGlobalOriginY, SPRING_CONFIG),
}
})
And then, let's use that in both of our Views:
// ...
<Animated.View
style={{ width: 100, height: 100, backgroundColor: 'green' }}
sharedTransitionTag="sharedTag"
sharedTransitionStyle={sharedElementTransition}
/>
// ...
Much better!
Conclusion
Reanimated Shared Element Transitions provide a great foundation for building smooth animations across screens, but as with all animations, use them sparingly as you don't want to overwhelm the user with moving things.
In a production app, you might use Shared Element Transitions for main Images, main Text, important Views and more. Animations are a way to underline the importance of some specific content in your app, for example in this app I'm working on, the user can view the details of a car.
The common view in both screens is the car's image, so I want to underline the importance of that image by animating it along with the screen transition. Here's how that looks:
As of Reanimated 3.0.2, Shared Element Transitions are not yet production ready. The Software Mansion team is taking feedback for the new feature, so make sure to try it out!
Shoutout to the @swmansion team for building this feature, I'm really excited for SETs!
wix/react-native-navigation
If you're using Wix's react-native-navigation library instead of react-navigation, you can already use SETs in your app!
I've worked on this quite a while ago, but here's a tweet showcasing react-native-navigation's Shared Element Transitions: https://twitter.com/mrousavy/status/1319232047764918272