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"
},
];
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.
Example using Text and Images
You can get started here:
Thanks.