Layout animations
Create layout and shared layout animations with React and Framer Motion.
CSS layouts are difficult and expensive to animate.
Animating a style like height
between 100px
and 500px
is conceptually straightforward, but suffers from poor performance because we're triggering the browser layout systems every animation frame.
Sometimes it doesn't even make sense. What does it actually mean to animate justify-content
between flex-start
and flex-end
?
Framer Motion can animate between any CSS layout by using performant transforms instead of the layout system.
For example, this component is animated by switching justify-content
between flex-start
and flex-end
.
To enable Framer Motion's layout animations, we simply set the layout
prop of a motion
component.
<motion.div layout />
Any layout change that happens as the result of a re-render will be animated. That could be any combination of:
- Reordering of a list.
- A style set on the component itself, for example a change in
width
orposition
. - A change in the parent's layout, e.g. flexbox or grid.
- Or any other change in the component's layout.
#Scale correction
All layout animations are performed using the transform
property, resulting in smooth framerates.
Animating layout using transforms can sometimes visually distort children. To correct this distortion, the first child elements of the element can also be given layout
property.
Try switching this component between layouts, with and without setting layout
on the pink dot:
Transforms can also distort boxShadow
and borderRadius
. The motion
component will automatically correct this distortion on both props, as long as they're set as motion values.
If you're not animating these values, the easiest way to do this is to set them via style
.
<motion.div layout style={{ borderRadius: 20 }} />
#Customising layout animations
Layout animations can be customised using the transition
property.
<motion.div layout transition={{ duration: 0.3 }} />
If you want to set a transition specifically for only the layout animation, you can specify a specific layout
transition.
<motion.div layout animate={{ opacity: 0.5 }} transition={{ opacity: { ease: "linear" }, layout: { duration: 0.3 } }}/>
#Animating within scroll containers
To animate layout correctly within scrollable elements, these elements must be given the layoutScroll
prop.
<motion.div layoutScroll style={{ overflow: "scroll" }}/>
This lets Framer Motion account for this element's scroll offset when measuring children.
#Coordinating layout animations
Layout animations are triggered when a component re-renders and its layout has changed.
function Accordion() { const [isOpen, setOpen] = useState(false) return ( <motion.div layout style={{ height: isOpen ? "100px" : "500px" }} onClick={() => setOpen(!isOpen)} /> )}
But what happens when we have two or more components that don't re-render at the same time, but do affect each other's layout?
function List() { return ( <> <Accordion /> <Accordion /> </> )}
When one re-renders, the other won't be able to detect changes to its layout.
We can synchronise layout changes across multiple components by wrapping them in the LayoutGroup component
.
import { LayoutGroup } from "framer-motion"
function List() { return ( <LayoutGroup> <Accordion /> <Accordion /> </LayoutGroup> )}
Now, when layout changes are detected in one grouped component, layout animations will happen across all of them. Without any extra re-renders.
#Shared layout animations
When a new component is added that has a layoutId
prop that matches an existing component, it will automatically animate out from the old component.
isSelected && <motion.div layoutId="underline" />
If the old component is still mounted when the new component enters, they will automatically crossfade from the old to the new.
#Troubleshooting
#The component isn't animating
Ensure the component is not set to display: inline
, as browsers don't apply transform
to these elements.
#SVG layout animations are broken
SVG components aren't currently supported with layout animations. SVGs don't have layout systems so it's recommended to directly animate their attributes like cx
etc.
#Skew transforms aren't taking effect
skew
transforms are not currently compatible with layout animations.
#The content stretches undesirably
This is a natural side-effect of animating width and height with scale. Some elements, like those containing elements changing between different aspect ratios (commonly text elements), might be better animated with layout="position"
, which only animates the position of the element.
#Border radius or box shadows are behaving strangely
Animating scale
is performant but can distort some styles like border-radius
and box-shadow
.
Framer Motion automatically corrects for this scale distortion but this correction is limited to border-radius
defined as pixels or percent, and a single box-shadow
.
#Sticky element isn't animating as expected
Elements with position: sticky
are difficult to animate as they flip between two coordinate spaces (page-relative and viewport-relative) without any way of detecting which should be used.
If children of the position: sticky
element are not animating as expected, it's possible to add layout layoutRoot
props to the sticky element. This will perform all the layout calculations and an instant layout animation on the sticky
element and all children will perform their layout animations relative to it.
<motion.header layout layoutRoot style={{ position: "sticky" }}> <motion.h1 layout /></motion.header>
#Item is performing unwanted layout animations as surrounding content changes
By adding layout layoutRoot
to a parent, children will perform all layout animations relative to the parent. See example.
<motion.div className="switch-container" layout layoutRoot> <motion.div className="switch-handle" layout /></motion.div>