AnimateSharedLayout
Animate layout changes across, and between, multiple components.
Note: AnimateSharedLayout
has been removed as of Framer Motion 5. Read the upgrade guide.
The AnimateSharedLayout
component enables you to perform layout animations:
- Across a set of components that don't otherwise share state.
- Between different components that share a
layoutId
as they're added/removed.
import { AnimateSharedLayout } from "framer-motion"
#Syncing layout animations
motion
components with a layout
prop will automatically animate layout changes that occur when they re-render.
<motion.div layout />
However, if a layout
component changes layout due to local state change, surrounding components need a way to know they should animate their layout, too.
function Item({ content }) { const [isOpen, setIsOpen] = useState(false)
return <motion.div layout>{isOpen && content}</motion.div>}
function List({ items }) { /** * This wrapping motion.ul, and sibling * Item components won't animate layout * when an Item opens/closes */ return ( <motion.ul layout> {items.map(item => ( <Item content={item.content} /> ))} </motion.ul> )}
By wrapping components with AnimateSharedLayout
, all layout
components within will inform each other when their layout changes, and animate accordingly.
function List({ items, selectedId }) { return ( <AnimateSharedLayout> <motion.ul layout> {items.map(item => ( <Item content={item.content} /> ))} </motion.ul> </AnimateSharedLayout> )}
#Animate between components
AnimateSharedLayout
can be used to animate between completely different motion
components that share the same layoutId
prop.
In this example, each item contains a motion
component with a layoutId="outline"
prop that gets rendered only if the item is selected.
isSelected && <motion.div layoutId="underline" />
When a new component with a layoutId
gets added as another gets removed, the component will perform a layout animation from previous component.
The new component will also inherit any animating values from the old component as its initial
state. So visually it'll be treated as one continuous component.
Note: If the previous component remains in the tree when the new one is added, it'll automatically be hidden using visibility: hidden
. If the new component is subsequently removed, the previous component will be set back to visible.
<motion.div layoutId="underline" initial={false} animate={{ backgroundColor: "#ff0000" }}/>
#Experimental
Note: The following features are considered experimental. Their API is stable, but due to the number of implementation permutations it's possible we haven't caught every bug or addressed every use-case. If you run into bugs or unexpected behaviours, please open an issue.
#Shared drag session
A motion
component can be provided both a drag
and a layoutId
prop.
If a component is being dragged when it's removed, and a new component with the same layoutId
is added elsewhere within the same AnimateSharedLayout
component, the drag gesture will continue on the new component.
<motion.div drag layoutId="box" />
#AnimatePresence
If a component with a layoutId
is added, and the existing component with that same layoutId
is still rendered, the older component will automatically be hidden. The new component will animate out from its position.
By wrapping the new component in AnimatePresence
, when it's removed it'll automatically animate back to the original component's position as an exit animation.
<AnimateSharedLayout> {images.map((img) => <motion.img layoutId={img.id} />)} <AnimatePresence> {selectedId && <motion.img layoutId={selectedId} />)} </AnimatePresence></AnimateSharedLayout>
#Crossfade
When animating between two components using AnimatePresence
, they might look quite different to each other.
For instance, they might be images with a different aspect ratio, or a text box with different text wrapping.
Switching instantly between the two components at the start and end of the animations might look odd.
By adding type="crossfade"
to AnimateSharedLayout
, immediate children of AnimatePresence
will crossfade with their old component, smoothing this transition.
<AnimateSharedLayout type="crossfade"> {images.map((img) => <motion.img layoutId={img.id} />)} <AnimatePresence> {selectedId && <motion.img layoutId={selectedId} />} </AnimatePresence></AnimateSharedLayout>
#Props
#type: "switch" | "crossfade"
When combined with AnimatePresence
, SharedLayoutProps
can customise how to visually switch between layoutId
components as new ones enter and leave the tree.
"switch"
(default): The old layoutId
component will be hidden instantly when a new one enters, and the new one will perform the full transition. When the newest one is removed, it will perform the full exit transition and then the old component will be shown instantly.
"crossfade"
: The root shared components will crossfade as layoutId
children of both perform the same transition.