Animation
How to animate in Framer Motion.
There are multiple ways to animate in Framer Motion, scaling to the complexity of your needs.
#Simple animations
Most animations will be performed with the motion
component and the animate
prop.
<motion.div animate={{ x: 100 }} />
When any value in animate
changes, the component will automatically animate to the updated target.
#Transitions
By default, Motion will create an appropriate animation for a snappy transition based on the types of value being animated. For instance, physical properties like x
or scale
will be animated via a spring simulation. Whereas values like opacity
or color
will be animated with a tween.
However, you can define different types of animation by passing a default transition to the transition
prop.
<motion.div animate={{ x: 100 }} transition={{ ease: "easeOut", duration: 2 }}/>
#Enter animations
When a motion
component is first created, it'll automatically animate to the values in animate
if they're different from those defined in style
or initial
. You can set the initial
prop to false
to disable enter animations.
<motion.div animate={{ x: 100 }} initial={false} />
#Exit animations
In React, when a component is removed from the tree, it's removed instantly. Framer Motion provides the AnimatePresence component
to keep components in the DOM while they perform an exit animation.
<AnimatePresence> {isVisible && ( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} /> )}</AnimatePresence>
#Keyframes
Values in animate
can also be set as a series of keyframes. This will animate through each value in sequence.
<motion.div animate={{ x: [0, 100, 0] }}/>
We can use the current value as the initial keyframe by passing a wildcard keyframe, null
.
<motion.div animate={{ x: [null, 100, 0] }}/>
This way, if a keyframes animation starts while the value is currently animating, the transition will be more natural. It also reduces duplication in our code.
<motion.circle cx={500} animate={{ cx: [null, 100] }} />
Each keyframe will be spaced evenly throughout the animaton. You can override this by setting the times
option via transition
.
times
is an array of the same length as the keyframes array, with numbers between 0
and 1
definining where in the animation each keyframe should be hit.
<motion.circle cx={500} animate={{ cx: [null, 100, 200] }} transition={{ duration: 3, times: [0, 0.2, 1] }}/>
#Gesture animations
Framer Motion has shortcuts for animating to a set of values when gestures start, like hover
, tap
, drag
, focus
and inView
:
<motion.button initial={{ opacity: 0.6 }} whileHover={{ scale: 1.2, transition: { duration: 1 }, }} whileTap={{ scale: 0.9 }} whileInView={{ opacity: 1 }}/>
It'll automatically figure out which values to animate back to when these gestures end.
#Variants
Setting animate
as an object is useful for simple, single-component animations. But sometimes we want to create animations that propagate throughout the DOM, and orchestrate those animations in a declarative way. We can do so with variants.
Variants are sets of pre-defined targets.
const variants = { visible: { opacity: 1 }, hidden: { opacity: 0 },}
They're passed into motion
components via the variants
prop.
<motion.div variants={variants} />
These variants can be referred to by label, wherever you can define an animation object.
<motion.div initial="hidden" animate="visible" variants={variants}/>
#Propagation
If a motion
component has children, changes in variant will flow down through the component hierarchy until a child component defines its own animate
property.
const list = { visible: { opacity: 1 }, hidden: { opacity: 0 },}
const item = { visible: { opacity: 1, x: 0 }, hidden: { opacity: 0, x: -100 },}
return ( <motion.ul initial="hidden" animate="visible" variants={list} > <motion.li variants={item} /> <motion.li variants={item} /> <motion.li variants={item} /> </motion.ul>)
#Orchestration
By default, all these animations will start simultaneously. But by using variants, we gain access to extra transition
props like when
, delayChildren
, and staggerChildren
that can let parents orchestrate the execution of child animations.
const list = { visible: { opacity: 1, transition: { when: "beforeChildren", staggerChildren: 0.3, }, }, hidden: { opacity: 0, transition: { when: "afterChildren", }, },}
#Dynamic variants
Each variant can be defined as a function that resolves when a variant is accessed. These variant functions are provided a single argument, which can be set in a component's custom
prop.
const variants = { visible: i => ({ opacity: 1, transition: { delay: i * 0.3, }, }), hidden: { opacity: 0 },}
return items.map((item, i) => ( <motion.li custom={i} animate="visible" variants={variants} />))
#Multiple variants
Props like animate
, whileHover
etc can define one or more variants by passing a string or an array of strings.
<motion.ul variants={["open", "primary"]} />
If variants define the same values, variants appearing later in the array will take precedence over those earlier in the array.
#Manual controls
Declarative animations are ideal for most UI interactions. But sometimes we need to orchestrate more complex sequences.
The useAnimate
hook can be used to:
- Animate any HTML/SVG element
- Create complex sequences of animations
- Control animations with
time
,speed
,play()
,pause()
and other playback controls.
const MyComponent = () => { const [scope, animate] = useAnimate() useEffect(() => { const animation = async () => { await animate(scope.current, { x: "100%" }) animate("li", { opacity: 1 }) } animation() }, [])
return ( <ul ref={scope}> <li /> <li /> <li /> </ul> )}
#Animate single values
It's also possible to use useAnimate to animate single values or a single MotionValue
.
const [scope, animate]= useAnimate()const x = useMotionValue(0)
useEffect(() => { const controls = animate(x, 100, { type: "spring", stiffness: 2000, onComplete: v => {} })
return () => controls.stop()})
#Animate content
We can render the current value of a MotionValue
by passing it as a motion
component's child.
const count = useMotionValue(0)const rounded = useTransform(count, latest => Math.round(latest))
useEffect(() => { const controls = animate(count, 100)
return () => controls.stop()}, [])
return <motion.div>{rounded}</motion.div>
#Hardware-accelerated animations
Browsers are able to run some animations via the GPU using CSS or the Web Animations API (WAAPI).
Running animations on the GPU enables smoother performance, especially in situations where the main JavaScript thread becomes busy. GPU animations are also more energy efficient, leading to lower battery usage.
However, native browser animation APIs offer fewer features than Framer Motion's JavaScript animations. For this reason, Motion's hybrid engine intelligently decides when an animation can safely run on the GPU, falling back to JavaScript animations when it needs the additional functionality.
It even does some work to ensure features that don't traditionally work on the GPU are supported, like spring animations, custom easing functions, velocity transfer and animation interruption.
#Supported values
Different browsers are capable of accelerating different values so Framer Motion supports a superset of them.
transform
opacity
clipPath
filter
Note on transform
: Motion allows animating independent transforms like x
and scale
, while browsers don't. Therefore, hardware acceleration only works when transform
itself is animated.
<motion.div animate={{ transform: "translateX(100px)" }}/>
The downside to this approach, as is the case with animating transform
via CSS, is all transform values have to be animated together. So it is recommended to normally use independent transforms for readability and flexibility, using transform
directly only in situations where you really need the accelerated animations.
#Unsupported features
Supported values will always use hardware-accelerated animations, unless:
- The
motion
component has ononUpdate
prop. - The value is passed in as a motion value via the
style
prop. repeatDelay
is set.repeatType
is set as"mirror"
.damping
is set to0
.
#Auto will-change
The will-change
CSS rule allows developers to inform the browser when a style like transform
and opacity
are likely to change. The browser uses this information to optimise rendering, for instance by making an element a composited layer and therefore avoiding repaints.
Framer Motion automatically manages will-change
by adding style names that can be browser-optimised.
Style names are added at the start of animations and gestures.
const x = useMotionValue(0)
// will-change: transform<motion.div style={{ x }} />
Once added, values aren't removed, to reduce the subtle rendering differences that can occur in Safari between GPU layers and non-layers.
will-change
can still be managed manually, via style
:
<motion.div animate={{ transform: "translateX(100px)" }} style={{ willChange: "auto" }}/>