DEV Community

Cover image for Hero animations in React with react-motion-layout
Jeff
Jeff

Posted on

Hero animations in React with react-motion-layout

Hello Devs.

A couple of days ago I published my first React package and I want to show you how to use it.

React-Motion-Layout

This library helps you animate components from two different React trees. In other words, to create Hero Animations. It's compatible with moderns browsers and uses the Element.animate() Web API.

Let's build one of my favorite examples, a photo gallery.

This is the final result

Click on any photo to see it in action.

Looks beautiful right? Let's take a look at how simple is to recreate this example.

1 - Create placeholder photos

Thanks to Unsplash for those amazing photos.

// PhotosDB.js
export default [
  {
    photo:
      "https://images.unsplash.com/photo-1474313438662-85ce389c174a?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=50"
  },
  {
    photo:
      "https://images.unsplash.com/photo-1521170665346-3f21e2291d8b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=50"
  },
  {
    photo:
      "https://images.unsplash.com/photo-1520512202623-51c5c53957df?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=50"
  },
];
Enter fullscreen mode Exit fullscreen mode

2 - Let's wrap our app with MotionLayoutProvider

Motion Layout Provider is responsible for providing the state management.

// App.js
...
export default function App() {
  return (
    <Router>
      <MotionLayoutProvider>
        <Switch>
          <Route path="/photo/:photoId">
            <Photo />
          </Route>
          <Route path="/">
            <Photos />
          </Route>
        </Switch>
      </MotionLayoutProvider>
    </Router>
  );
}
Enter fullscreen mode Exit fullscreen mode

3 - Create The Photos Component

Since this is an individual screen, we'll wrap it using MotionScreen to clean registered elements when abandoning this screen.

import { MotionScreen } from 'react-motion-layout';
export default function Photos() {
  return (
    <MotionScreen>
      <div className="flex flex-wrap">
         {PhotosDB.map((item, id) => (
           <ItemComponent item={item} id={id} key={id} />
         ))}
      </div>
    </MotionScreen>
  );
}
Enter fullscreen mode Exit fullscreen mode

4 - The single photo item

Each item will be wrapped with a MotionScene. A MotionScene is a component that contains SharedElements.

SharedElements are the components that we will animate. They must have an unique key called animationKey, we use that key to find a matching SharedElement when changing the views.


MotionScene accepts an onClick property, in this case we are using the withTransition hook, that will trigger the animation and then will change the route using the history hook provided by react-router-dom.

...
import { useMotion, MotionScene, SharedElement } from 'react-motion-layout';

// PhotoItem.js
export default function ItemComponent({ item, id }) {
  const history = useHistory();
  const withTransition = useMotion(`photo-${id}`);
  const callback = useCallback(() => history.push(`/photo/${id}`), [
    history,
    id
  ]);

  return (
    <MotionScene name={`photo-${id}`} onClick={withTransition(callback)}>
      <div className="p-4 cursor-pointer hover:bg-gray-100">
        <SharedElement.Image
          className="w-64"
          alt=""
          src={item.photo}
          animationKey="image"
        />
      </div>
    </MotionScene>
  );
}
Enter fullscreen mode Exit fullscreen mode

5 - The individual photo view

The Story View is wrapped by a MotionScreen since it represents a single screen. And of course, it could contain more than a single Scene.

Since it's just one scene, we will wrap it with MotionScene as well, when navigating, those scenes will match and the Package with look for the declared SharedComponents and match them using its keys. then, it will perform the animation.

...
import { useParams } from "react-router-dom";
import PhotosDB from "./PhotosDB";

import { MotionScene, MotionScreen, SharedElement } from "react-motion-layout";

export default function Photo() {
  const { photoId } = useParams();
  const item = PhotosDB[photoId || 0];

  return (
    <MotionScreen>
      <MotionScene name={`photo-${photoId}`}>
        <div className="flex flex-col p-8">
          <SharedElement.Image
            className="w-64"
            alt=""
            src={item.photo}
            animationKey="image"
          />
        </div>
      </MotionScene>
    </MotionScreen>
  );
}
Enter fullscreen mode Exit fullscreen mode

And that's it

Now when you click on any item of the gallery it should animate using the shared components we'd just defined.

Motion Layout Docs
Github
Example using Text

Thanks.

Top comments (2)

Collapse
 
joshuaamaju profile image
Joshua Amaju

It works only with images?

Collapse
 
jeffersonlicet profile image
Jeff

Currently, it supports images and text