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",
                        }}
                        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:


Join the Framer book mailing list    ( ± 6 emails/year )

GDPR

We use Mailchimp as our marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing per their Privacy Policy and Terms.



Leave a Reply