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

Hero animations in React with react-motion-layout

jeffersonlicet profile image Jeff ・3 min read

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"
  },
];

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>
  );
}

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>
  );
}

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>
  );
}

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>
  );
}

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.

Discussion

pic
Editor guide
 

It works only with images?

 

Currently, it supports images and text