Animation » Example Animations » 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",
                        }}
                        onTap={() => {
                            setFormerColor(tabs[selected].color)
                            setSelected(i)
                        }}
                    >
                        {name}
                        {i === selected && (
                            <motion.div
                                style={selectionStyle}
                                layoutId="selected"
                                initial={{ backgroundColor: formerColor }}
                                animate={{ backgroundColor: color }}
                            />
                        )}
                    </motion.div>
                ))}
            </div>
        </div>
    )
}

const containerStyle: React.CSSProperties = {
    borderRadius: 21,
    backgroundColor: "#3a55f9",
    padding: 6,
    display: "flex",
    alignContent: "flex-start",
    alignItems: "start",
    justifyContent: "start",
    zIndex: 1,
}

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%",
    borderRadius: 15,
    position: "absolute",
    top: 0,
    left: 0,
    zIndex: -1,
}

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,
                                    })
                                }}
                            >
                                {name}
                                {i === store.selected && (
                                    <motion.div
                                        style={selectionStyle}
                                        layoutId="selected"
                                        initial={{
                                            backgroundColor: store.formerColor,
                                        }}
                                        animate={{ backgroundColor: color }}
                                    />
                                )}
                            </motion.div>
                        ))}
                    </div>
                }
            />
        )
    }
}

const containerStyle: React.CSSProperties = {
    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",
    zIndex: 0,
}

const selectionStyle: React.CSSProperties = {
    width: "100%",
    height: "100%",
    borderRadius: 15,
    position: "absolute",
    top: 0,
    left: 0,
    zIndex: -1,
}

Other examples of using children in a code override:


Leave a Reply