Some Examples » 34. Swapping elements
34. Swapping elements
By giving elements the same layoutId
, you can animate between them.
Code component
Here the colored pills behind the options (with a style
of selectionStyle
) all have the same layoutId
value: "selected"
.
When the value of selected
changes, one pill will be removed and another added.
const tabs = [
{ name: "Red", color: "#f00" },
{ name: "Purple", color: "#b1f" },
{ name: "Orange", color: "#f90" },
{ name: "Green", color: "#0c0" },
]
export default function CC_34_Swapping_elements(props) {
const [selected, setSelected] = useState(0)
const [formerColor, setFormerColor] = useState(tabs[0].color)
return (
<div
style={{
width: 400,
height: 400,
...props.style,
display: "flex",
placeItems: "center",
placeContent: "center",
}}
>
<div style={containerStyle}>
{tabs.map(({ name, color }, i) => (
<motion.div
style={tabStyle}
key={i}
initial={{
color: i === selected ? "#fff" : "#222",
}}
animate={{
color: i === selected ? "#fff" : "#222",
}}
transition={{ duration }}
onTap={() => {
setFormerColor(tabs[selected].color)
setSelected(i)
}}
>
<span style={{ position: "relative", zIndex: 1 }}>
{name}
</span>
{i === selected && (
<motion.div
style={selectionStyle}
layoutId="selected"
initial={{ backgroundColor: formerColor }}
animate={{ backgroundColor: color }}
/>
)}
</motion.div>
))}
</div>
</div>
)
}
const containerStyle: React.CSSProperties = {
position: "relative",
borderRadius: 21,
backgroundColor: "rgba(255, 255, 255, 0.2)",
padding: 6,
display: "flex",
alignContent: "flex-start",
alignItems: "start",
justifyContent: "start",
}
const tabStyle: React.CSSProperties = {
height: 30,
position: "relative",
padding: "3px 15px",
margin: 0,
fontFamily: "sans-serif",
fontSize: 20,
fontWeight: 700,
color: "#222",
cursor: "pointer",
}
const selectionStyle: React.CSSProperties = {
width: "100%",
height: "100%",
position: "absolute",
borderRadius: 15,
top: 0,
left: 0,
}
Code override
To trigger this kind of layout animations, you need to remove an element from the DOM and add another one. An override can’t remove a layer from the canvas. So here I used children
to add all elements to a frame that’s already on the canvas.
const tabs = [
{ name: "Red", color: "#f00" },
{ name: "Purple", color: "#b1f" },
{ name: "Orange", color: "#f90" },
{ name: "Green", color: "#0c0" },
]
const useStore = createStore({ selected: 0, formerColor: tabs[0].color })
export function Swapping_elements(Component): ComponentType {
return (props) => {
const [store, setStore] = useStore()
return (
<Component
{...props}
children={
<div style={containerStyle}>
{tabs.map(({ name, color }, i) => (
<motion.div
style={tabStyle}
key={i}
initial={{
color:
i === store.selected ? "#fff" : "#222",
}}
animate={{
color:
i === store.selected ? "#fff" : "#222",
}}
onTap={() => {
setStore({
formerColor: tabs[store.selected].color,
selected: i,
})
}}
>
<span
style={{ position: "relative", zIndex: 1 }}
>
{name}
</span>
{i === store.selected && (
<motion.div
style={selectionStyle}
layoutId="selected"
initial={{
backgroundColor: store.formerColor,
}}
animate={{ backgroundColor: color }}
/>
)}
</motion.div>
))}
</div>
}
/>
)
}
}
const containerStyle: React.CSSProperties = {
position: "relative",
borderRadius: 21,
padding: 6,
display: "flex",
alignContent: "flex-start",
alignItems: "start",
justifyContent: "start",
}
const tabStyle: React.CSSProperties = {
height: 30,
position: "relative",
padding: "3px 15px",
margin: 0,
fontFamily: "sans-serif",
fontSize: 20,
fontWeight: 700,
color: "#222",
cursor: "pointer",
}
const selectionStyle: React.CSSProperties = {
width: "100%",
height: "100%",
position: "absolute",
borderRadius: 15,
top: 0,
left: 0,
}
Other examples of using children
in a code override:
- 20. SVG path length
- 22. Keyframes: Morphing an SVG path
- 30. SVG gradient animation
- 31. Animate Presence