Shared Element Transition
On Android, our example email app has a shared element transition that runs when a user opens a mail. The title of the email smoothly animates from the 'inbox' scene to the 'mail' scene. We want that same shared element transition to run on the web, too. We've already wrapped the subject elements in SharedElement
components. On the web, each SharedElement
needs a data prop
. It holds the subject line and font size so that we can scale the text during the scene navigation.
// The 'inbox' scene
<SharedElement
data={{size: 14, subject: subject}}
name={'email' + id}>
<Text>{subject}</Text>
</SharedElement>
// The 'mail' scene
<SharedElement
data={{size: 20, subject: subject}}
name={'email' + id}>
<Text>{subject}</Text>
</SharedElement>
Animating
During a navigation, the NavigationStack.SharedElementTransition
component keeps track of the shared element between the two scenes. As the navigation progresses, it interpolates between the source and target element screen positions. It calls you back with a style
parameter containing these interpolated values so you can animate the shared element. The SharedElementTransition
component also accepts onAnimating
and onAnimated props
so you can style the source and target elements when the navigation starts and finishes.
Returning to the email example, we create a MailShared
component that renders a SharedElementTransition
component. Inside the child callback function we render the email subject inside a div. We style the div using the interpolated coordinates. To make it look like the subject is moving out of the 'inbox' scene and into the 'mail' scene, we supply an onAnimating prop
that hides the subject on these scenes when the navigation begins. An onAnimated prop
restores their visibility when the navigation completes.
const MailShared = (props) => (
<NavigationStack.SharedElementTransition
onAnimating={(name, ref) => {ref.style.opacity = 0}}
onAnimated={(name, ref) => {ref.style.opacity = 1}}
{...props}>
{(style, name, data) => (
<Text
key={name}
style={{
position: 'absolute',
fontSize: `${data.size}px`,
left: data.left,
top: data.top,
width: data.width,
height: data.height,
transformOrigin: 'top left',
transform: `
translate(${style.left - data.left}px, ${style.top - data.top}px)
scale(${style.width / data.width}, ${style.height / data.height})
`
}}>
{data.subject}
</Text>
)}
</NavigationStack.SharedElementTransition>
);
We assign our MailShared
component to the sharedElementTransition prop
of the NavigationStack
component because the shared element transition has to run as part of the overall scene animation.
<NavigationStack
sharedElementTransition={props => <MailShared {...props} />}
...