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} />}
  ...