If there's one feature that makes framer-motion an absolute kick-ass framework, it's variants.
Variants are among the most powerful animation tools for frontend development. What exactly are they? Basically they're pre-defined animation states that you can re-use in your framer animations.
They look something like this:
import { motion } from "framer-motion"
<motion.div variants={{
start: {opacity: 0, rotate: 0}
end: {opacity: 1, rotate: 360}
}} />
To break down what we're looking at:
- We define variants using a
variants
keyword on amotion
component - Within
variants
, we define keys and within those keys, we define some animation states - The names of your keys are the names of your variants
đĻ Variants are just animation states
So basically if we translated the above motion
component's variants to English, we can say it has two variants: a start
and end
state. We can then refer to these variants as a string to do actual animations like this:
<motion.div
variants={{
start: {opacity: 0, rotate: 0}
end: {opacity: 1, rotate: 360}
}}
initial="start"
animate="end"
/>
That's really awesome. Basically variants let us reduce (or rather clean up) the animation syntax for motion
components. The cool part about variants is that they're just regular JavaScript objects, you can define them anywhere in your React component!
đ You can define variants anywhere
Here's an example of that:
import { motion } from "framer-motion"
const variants = {
open: { ... },
close: { ... }
}
export const Component = () => (
return <motion.div variants={variants}> ... </motion.div>
)
motion
component since, for the most part, variants aren't very big. If you're dealing with variants that have a ton of keys then you probably want to extract it out to a variable and pass it in.đĸ You can define as many variants as you want
Don't think you're just restricted to two variants for each of your framer animations. Variants are kinda arbitrary, literally they are just an object of string keys and framer animations, you can define as many of these keys as you want!
Let's say you want to build an animation that has 4 different variants and each one is activated depending on some state.
import { useState } from "react"
import { motion } from "framer-motion"
export const MyComponent = () => {
const [state, setState] = useState("");
const variants = {
first_state: { opacity: "0%" },
second_state: { scale: 2 },
third_state: { rotate: 360},
fourth_state: { skew: 99 }
}
return (
<div>
<button onClick={() => setState("first_state")}>Click me for State 1</button>
<button onClick={() => setState("second_state")}>Click me for State 2</button>
<button onClick={() => setState("third_state")}>Click me for State 3</button>
<button onClick={() => setState("fourth_state")}>Click me for State 4</button>
<motion.div variants={variants} animate={variants[state]} />
</div>
)
}
đ˛ Variants get inherited to create complex animations and timelines
One of the best features of variants are that they're inherited! This allows you to create some pretty complex animations and timelines. We'll actually cover this in more detail in the next tutorial on timeline animations and keyframing, for now let's just look at how this works.
By inherited, I mean if you pass a motion
component as a child of another motion
component with a variant(s), then the child component has access to those variants.
Here's what I'm talking about:
import { motion } from "framer-motion"
<motion.div variants={{open: {opacity: "100%"}, closed: {opacity: "0%"}}} initial="closed" animate="open">
<motion.h1 variants={{open: {scale: 2}, closed: {scale: 0}}}>Hey I'm content!</motion.h1>
</motion.div>
In the example above we defined a motion
component that has some open
and closed
variants (which toggle its opacity really). Within this motion component, we define another motion
component and set some content inside of it as well as some more variants to scale it.
Now, notice how the variants
between the parent and child motion
components are 1:1? That's on purpose. Also notice we've set the initial
state of this animation to be closed
and are setting animate
to open
.
Alright, so the really cool part about variants is this: if your child motion components use the exact same variants names as its parent, then any initial
and animate
props defined on that parent are automatically passed down to its children.
What does that really mean? It means that we've created a timeline animations where we'll fade in our parent motion
component and, automatically, our scaling animation on the h1
component will play automagically, thanks for variants inheritance by framer-motion.
...it also means that the animations run at the exact same time, which may not be ideal (again next tutorial we'll talk a lot more about timelines, keyframing, etc.).
đ Wrapping up
In this tutorial we quickly covered the use of variants in framer-motion! You should now be using variants
wherever possible over defining your animations within initial
/ animate
. In the next tutorial of this series, we'll talk about timelines and keyframing in order to build some really fun animations.