With the Framer Motion library, we can render animations in our React app easily.
In this article, we’ll take a look at how to get started with Framer Motion.
Shared Layout Animations
We can create shared layout animations with the AnimateSharedLayout
component.
For example, we can write:
App.js
import React, { useState } from "react";
import { AnimatePresence, AnimateSharedLayout, motion } from "framer-motion";
import "./styles.css";
function Item({ text }) {
const [isOpen, setIsOpen] = useState(false);
const toggleOpen = () => setIsOpen(!isOpen);
return (
<motion.li layout onClick={toggleOpen} initial={{ borderRadius: 10 }}>
<motion.div layout>{text}</motion.div>
<AnimatePresence>{isOpen && <Content />}</AnimatePresence>
</motion.li>
);
}
function Content() {
return (
<motion.div
layout
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<div className="row" />
<div className="row" />
<div className="row" />
</motion.div>
);
}
const items = [
{ text: "foo", id: 1 },
{ text: "bar", id: 2 },
{ text: "baz", id: 3 }
];
export default function App() {
return (
<AnimateSharedLayout>
<motion.ul layout>
{items.map(({ text, id }) => (
<Item text={text} key={id} />
))}
</motion.ul>
</AnimateSharedLayout>
);
}
styles.css
html,
body {
min-height: 100vh;
padding: 0;
margin: 0;
}
* {
box-sizing: border-box;
}
body {
background-repeat: no-repeat;
display: flex;
justify-content: center;
align-items: center;
}
.App {
font-family: sans-serif;
text-align: center;
}
ul,
li {
list-style: none;
margin: 0;
padding: 0;
}
ul {
width: 300px;
display: flex;
flex-direction: column;
background: white;
padding: 20px;
border-radius: 25px;
}
li {
background-color: rgba(214, 214, 214, 0.5);
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
overflow: hidden;
cursor: pointer;
}
li:last-child {
margin-bottom: 0px;
}
.avatar {
width: 40px;
height: 40px;
background-color: #666;
border-radius: 20px;
}
.row {
width: 100%;
height: 8px;
background-color: #999;
border-radius: 10px;
margin-top: 12px;
}
We have the Item
component that lets us toggle the content of the div.
This is done with the AnimatPresence
component which shows the Content
component’s content if it’s true
.
The row
class creates the bars that we want to show.
In App
, we have the AnimateSharedLayout
component wrapped around our ul
to let us show the same animation for each li
.
The animation is synced across a set of components that don’t share state.
And they let us perform animation between different components with a common layoutId
as they’re added or removed.
When a component with the 1ayoutId
prop is removed from one part of the tree and it’s added elsewhere, the new component will automatically animate from the old component’s position.
For example, we can write:
App.js
import React, { useState } from "react";
import { AnimateSharedLayout, motion } from "framer-motion";
import "./styles.css";
export default function App() {
const [selected, setSelected] = useState(colors[0]);
return (
<AnimateSharedLayout>
<ul>
{colors.map((color) => (
<Item
key={color}
color={color}
isSelected={selected === color}
onClick={() => setSelected(color)}
/>
))}
</ul>
</AnimateSharedLayout>
);
}
function Item({ color, isSelected, onClick }) {
return (
<li className="item" onClick={onClick} style={{ backgroundColor: color }}>
{isSelected && (
<motion.div
layoutId="outline"
className="outline"
initial={false}
animate={{ borderColor: color }}
transition={spring}
/>
)}
</li>
);
}
const colors = ["red", "green", "blue", "yellow"];
const spring = {
type: "spring",
stiffness: 500,
damping: 30
};
styles.css
html,
body {
min-height: 100vh;
padding: 0;
margin: 0;
}
* {
box-sizing: border-box;
}
body {
background-repeat: no-repeat;
display: flex;
justify-content: center;
align-items: center;
}
.App {
font-family: sans-serif;
text-align: center;
}
ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
width: 280px;
height: 380px;
}
.item {
width: 100px;
height: 50px;
margin: 20px;
position: relative;
cursor: pointer;
flex-shrink: 0;
}
.outline {
position: absolute;
top: -20px;
left: -20px;
right: -20px;
bottom: -20px;
border: 10px solid white;
}
We add a frame around the li
that’s clicked on by toggling the isSelected
state in the Item
component.
In styles.css
, we have the outline
class to define the outline.
That is toggled when we toggle the li
.
In the App
component, we select the item that we clicked on with the setSelected
function.
Conclusion
We can add shared layouts animation to perform animations synced across a set of components that don’t share states.
And we can also perform animations between different components with a common layoutId
as they’re added or removed.