Shared Element Navigation
Sharing Elements
The Navigation router lets you smoothly transition between elements that are shared across two scenes when the user navigates from one scene to the other. It's a technique common to native apps, for example, zooming into an enlarged image when a user clicks on a thumbnail in a gallery. To identify elements you want to share across two scenes, you wrap each one in a SharedElement
component and give them both the same name
. You pass in a prop
holding the data you want to animate as one element transitions to the other.
In our email example app, we can smooth out the navigation when a user opens an email by sharing the subject of the email between the 'inbox' scene and the 'mail' scene. We wrap the subject elements on each scene inside a SharedElement
. The font size of the subject is smaller in the inbox than on the open email, 14px as compared to 20px. Because we need to scale the text during the navigation, we pass these font sizes, along with the subject line, into the data props
.
import {SharedElement} from 'navigation-react-mobile';
// The 'inbox' scene
<SharedElement
data={{size: 14, subject: mail.subject}}
name={`mail${mail.id}`}>
<span>{mail.subject}</span>
</SharedElement>
// The 'mail' scene
<SharedElement
data={{size: 20, subject: mail.subject}}
name={`mail${mail.id}`}>
<span>{mail.subject}</span>
</SharedElement>
Animating
During a navigation, the SharedElementMotion
component keeps track of elements that are shared between the two scenes. For each pair it finds, 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 SharedElementMotion
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 SharedElementMotion
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.
import {SharedElementMotion} from 'navigation-react-mobile';
const MailShared = (props) => (
<SharedElementMotion
onAnimating={(name, ref) => {ref.style.opacity = 0}}
onAnimated={(name, ref) => {ref.style.opacity = 1}}
{...props}>
{(style, name, data) => (
<div
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}
</div>
)}
</SharedElementMotion>
);
We assign our MailShared
component to the sharedElementMotion prop
of the NavigationMotion
component because the shared element animation has to run as part of the overall scene animation.
<NavigationMotion
unmountedStyle={{opacity: 0}}
mountedStyle={{opacity: 1}}
crumbStyle={{opacity: 0}}
sharedElementMotion={props => <MailShared {...props} />}
renderMotion={(style, scene, key) => (
<div
key={key}
style={{opacity: style.opacity}}>
{scene}
</div>
)}>
Note
Thestyle
parameter of the callback contains the interpolated data props
and screen positions. You can directly set them onto the shared element's style props
instead of using transform
, but the animation might be jankier.