Framer X » Animation » Example animations

Example animations

For the launch of Playground (Framer X22), Benjamin den Boer created a project with 30 examples that demonstrate the new animation APIs:

http://bit.ly/playground-examples

Benjamin used Code Components to make the examples. I recreated them using Overrides, and you can find both versions of each animation side by side in the project below.

Animations – Example animations – Code Components & Overrides.framerx
download the example project

Have fun with them by copying the animations and changing properties.

1. Animate

Just passing an object with visual properties to animate will trigger an animation.

And with the transition property, you define how things should animate, like e.g., which duration the animation should have.

Code Component

import * as React from "react"
import { Frame } from "framer"

export function Animation() {
    return (
        <Frame
            animate={{ rotate: 360 }}
            transition={{ duration: 2 }}
            size={150}
            radius={30}
            background="#fff"
            center={true}
        />
    )
}

Override

import { Override } from "framer"

export function Animate(): Override {
    return {
        animate: { rotate: 360 },
        transition: { duration: 2 },
    }
}

2. Animate: Spring

A spring animation that also starts automatically.

Code Component

export function AnimationSpring() {
    return (
        <Frame
            animate={{ rotate: 180 }}
            transition={{
                type: "spring",
                damping: 10,
                mass: 0.75,
                stiffness: 100,
            }}
            size={150}
            radius={30}
            background="#fff"
            center={true}
        />
    )
}

Override

export function Animate_Spring(): Override {
    return {
        animate: { rotate: 180 },
        transition: {
            type: "spring",
            damping: 10,
            mass: 0.75,
            stiffness: 100,
        },
    }
}

3. Animate: Loop

Use loop and Infinity to create never-ending animations.

Code Component

export function AnimationLoop() {
    return (
        <Frame
            animate={{ rotate: 360 }}
            transition={{ ease: "linear", duration: 2, loop: Infinity }}
            size={150}
            radius={30}
            background="#fff"
            center={true}
        />
    )
}

Override

export function Animate_Loop(): Override {
    return {
        animate: { rotate: 360 },
        transition: { ease: "linear", duration: 2, loop: Infinity },
    }
}

4. Animate: Reverse

Use yoyo to reverse an animation.

(There’s also flip, which only swaps the to and from values and not the animation curve.)

Code Component

export function AnimationReversed() {
    return (
        <Frame
            animate={{ rotate: 360 }}
            transition={{ duration: 2, yoyo: Infinity }}
            size={150}
            radius={30}
            background="#fff"
            center={true}
        />
    )
}

Override

export function Animate_Reverse(): Override {
    return {
        animate: { rotate: 360 },
        transition: { duration: 2, yoyo: Infinity },
    }
}

5. Event: WhileTap

With whileTap it’s easy to animate between two states.

Code Component

export function WhileTap() {
    return (
        <Frame
            whileTap={{ rotate: 90, scale: 0.75 }}
            size={150}
            radius={30}
            background="#fff"
            center={true}
        />
    )
}

Override

export function Event_WhileTap(): Override {
    return {
        whileTap: { rotate: 90, scale: 0.75 },
    }
}

6. Event: Hover

You’ve heard of whileTap? Here’s whileHover.

Code Component

export function Hover() {
    return (
        <Frame
            whileHover={{ scale: 0.8 }}
            size={150}
            radius={30}
            background="#fff"
            center={true}
        />
    )
}

Override

export function Event_Hover(): Override {
    return {
        whileHover: { scale: 0.8 },
    }
}

7. Event: Tap

Here Framer’s useCycle() hook is used to cycle between two visual states.

Code Component

export function Cycle() {
    const [animate, cycle] = useCycle(
        { scale: 1, rotate: 0 },
        { scale: 1.25, rotate: 90 }
    )
    return (
        <Frame
            animate={animate}
            onTap={() => cycle()}
            size={150}
            borderRadius={30}
            background="#fff"
            center={true}
        />
    )
}

Override

export function Event_Tap(): Override {
    const [animate, cycle] = useCycle(
        { scale: 1, rotate: 0 },
        { scale: 1.25, rotate: 90 }
    )
    return {
        animate: animate,
        onTap() {
            cycle()
        },
    }
}

8. Event: Sequence

With the useAnimation() hook you can start and stop animations. Combined with async / await this enables you to create a sequence of animations.

Code Component

export function Sequence() {
    const animation = useAnimation()

    async function sequence() {
        await animation.start({ rotate: -90 })
        await animation.start({ scale: 1.5 })
        await animation.start({ rotate: 0 })
        await animation.start({ scale: 1 })
    }

    return (
        <Frame
            size={150}
            radius={30}
            background="#fff"
            center={true}
            onTap={sequence}
            animate={animation}
        />
    )
}

Override

export function Event_Sequence(): Override {
    const animation = useAnimation()

    async function sequence() {
        await animation.start({ rotate: -90 })
        await animation.start({ scale: 1.5 })
        await animation.start({ rotate: 0 })
        await animation.start({ scale: 1 })
    }

    return {
        animate: animation,
        onTap() {
            sequence()
        },
    }
}

9. Drag

Just set drag to true (or "x", or "y") to make a Frame draggable.

Code Component

export function Drag() {
    return (
        <Frame
            drag={true}
            size={150}
            radius={30}
            background="#fff"
            center={true}
        />
    )
}

Override

export function Drag(): Override {
    return {
        drag: true,
    }
}

10. Drag: Inertia

Set up dragConstraints and you get the jump-back animation for free.

Code Component

export function DragInertia() {
    return (
        <Frame
            drag={true}
            size={150}
            radius={30}
            background="#fff"
            center={true}
            dragConstraints={{ top: 0, right: 0, bottom: 0, left: 0 }}
            dragTransition={{ bounceStiffness: 600, bounceDamping: 20 }}
            dragElastic={0.5}
        />
    )
}

Override

export function Drag_Inertia(): Override {
    return {
        drag: true,
        dragConstraints: { top: 0, right: 0, bottom: 0, left: 0 },
        dragTransition: { bounceStiffness: 600, bounceDamping: 20 },
        dragElastic: 0.5,
    }
}

11. Drag: Locking

Limit dragging to one direction by switching on dragDirectionLock.

Code Component

export function DragLocked() {
    return (
        <Frame
            drag={true}
            dragDirectionLock={true}
            size={150}
            radius={30}
            background="#fff"
            center={true}
            dragConstraints={{ top: 0, right: 0, bottom: 0, left: 0 }}
            dragTransition={{ bounceStiffness: 600, bounceDamping: 20 }}
            dragElastic={0.25}
        />
    )
}

Override

export function Drag_Locking(): Override {
    return {
        drag: true,
        dragDirectionLock: true,
        dragConstraints: { top: 0, right: 0, bottom: 0, left: 0 },
        dragTransition: { bounceStiffness: 600, bounceDamping: 20 },
        dragElastic: 0.25,
    }
}

12. Drag: Transform

This example uses the useMotionValue() and useTransform() hooks to transform the drag position to scale and rotation values. And it uses the useAnimation() hook to trigger a jump-back animation.

Code Component

export function DragTransform() {
    const x = useMotionValue(0)
    const scale = useTransform(x, [-150, 150], [1.5, 0.5])
    const rotate = useTransform(x, [-150, 150], [-90, 90])

    const controls = useAnimation()

    function handleDragEnd() {
        controls.start({ x: 0 })
    }

    return (
        <Frame
            drag={"x"}
            x={x}
            animate={controls}
            scale={scale}
            size={150}
            rotate={rotate}
            radius={30}
            background="#fff"
            center={true}
            onDragEnd={handleDragEnd}
        />
    )
}

Override

export function Drag_Transform(): Override {
    const x = useMotionValue(0)
    const scale = useTransform(x, [-150, 150], [1.5, 0.5])
    const rotate = useTransform(x, [-150, 150], [-90, 90])

    const controls = useAnimation()

    return {
        drag: "x",
        x: x,
        animate: controls,
        scale: scale,
        rotate: rotate,
        onDragEnd() {
            controls.start({ x: 0 })
        },
    }
}

13. Drag: 3D

Comparable to the Drag: Transform example above, with the difference that the Frame jumps back automatically because of its dragConstraints (no useAnimation() is used here).

Code Component

export function Drag3D() {
    const x = useMotionValue(0)
    const y = useMotionValue(0)
    const rotateX = useTransform(x, [-100, 100], [-60, 60])
    const rotateY = useTransform(y, [-100, 100], [60, -60])

    return (
        <Frame
            size={100}
            borderRadius="50%"
            center
            style={{
                background:
                    "radial-gradient(rgba(255,255,255,0), rgba(255,255,255,0.3))",
            }}
        >
            <Frame
                x={x}
                y={y}
                size={150}
                borderRadius={30}
                background="#fff"
                drag={true}
                dragConstraints={{ left: 0, top: 0, right: 0, bottom: 0 }}
                center={true}
                rotateY={rotateX}
                rotateX={rotateY}
                z={100}
                dragElastic={0.6}
            />
        </Frame>
    )
}

Override

export function Drag_3D_circle(): Override {
    return {
        style: {
            background:
                "radial-gradient(rgba(255,255,255,0), rgba(255,255,255,0.3))",
        },
    }
}

export function Drag_3D(): Override {
    const x = useMotionValue(0)
    const y = useMotionValue(0)
    const rotateX = useTransform(y, [-100, 100], [60, -60])
    const rotateY = useTransform(x, [-100, 100], [-60, 60])

    return {
        x: x,
        y: y,
        z: 100,
        drag: true,
        dragConstraints: { left: 0, top: 0, right: 0, bottom: 0 },
        dragElastic: 0.6,
        rotateX: rotateX,
        rotateY: rotateY,
    }
}

14. Page

There’s no Override version of this example, simply because you only need the Page tool to create it on the Canvas.

Code Component

export function Paging() {
    return (
        <Page width={150} height={150} radius={30} center={true}>
            <Frame size={150} radius={30} background="#fff" />
            <Frame size={150} radius={30} background="#fff" />
            <Frame size={150} radius={30} background="#fff" />
        </Page>
    )
}

15. Page: Indicators

The Code Component version uses React’s useState() hook. In the Override version, Framer’s Data object is used to communicate between the three distinct Overrides.

Code Component

const pages = [1, 2, 3, 4, 5]
const indicatorSize = 10
const indicatorPadding = 10
const indicatorWidth = pages.length * indicatorSize
const indicatorPaddingTotal = (pages.length - 1) * indicatorPadding
const indicatorWidthTotal = indicatorWidth + indicatorPaddingTotal
const indicatorAlpha = 0.3

export function PageIndicators() {
    const [current, setCurrent] = useState(0)

    return (
        <div>
            <Page
                width={150}
                height={150}
                radius={30}
                center={true}
                onChangePage={(current, previous) => {
                    setCurrent(current)
                }}
            >
                {pages.map(index => {
                    return (
                        <Frame
                            key={index}
                            size={150}
                            radius={30}
                            background="#fff"
                        />
                    )
                })}
            </Page>

            {pages.map(index => {
                return (
                    <Frame
                        key={index}
                        size={indicatorSize}
                        style={{
                            top: `calc(50% + 100px)`,
                            left: `calc(50% + ${index - 1} * ${indicatorSize +
                                indicatorPadding}px) `,
                        }}
                        x={-indicatorWidthTotal / 2}
                        radius={30}
                        background="#fff"
                        opacity={indicatorAlpha}
                        animate={{
                            opacity: current === index - 1 ? 1 : indicatorAlpha,
                        }}
                    />
                )
            })}
        </div>
    )
}

Override

const data = Data({
    currentIndicator: "",
})

let indicators = []
export function indicatorsContainer(props): Override {
    indicators = props.children as any
    return
}

export function indicator(props): Override {
    let currentVariant = "inactive"
    if (data.currentIndicator === "") {
        data.currentIndicator = props.id
    }
    if (data.currentIndicator === props.id) {
        currentVariant = "active"
    }
    return {
        variants: {
            inactive: { opacity: 0.3 },
            active: { opacity: 1 },
        },
        animate: currentVariant,
    }
}

export function Page_Indicators(): Override {
    return {
        onChangePage(currentIndex) {
            data.currentIndicator = indicators[currentIndex].props.id
        },
    }
}

16. Page: Effects

The effect property needs to contain a function that returns properties.

Code Component

const pages = [1, 2, 3, 4, 5]

export function PageEffects() {
    return (
        <div>
            <Page
                width={150}
                height={150}
                radius={30}
                center={true}
                gap={0}
                effect={info => {
                    const convertScale = transform([-1, 0, 1], [0.25, 1, 0.25])
                    const scale = convertScale(info.normalizedOffset)
                    const convertOpacity = transform([-1, 0, 1], [0, 1, 0])
                    const opacity = convertOpacity(info.normalizedOffset)

                    return { opacity, scale }
                }}
            >
                {pages.map(index => {
                    return (
                        <Frame
                            key={index}
                            size={150}
                            radius={30}
                            background={"#fff"}
                        />
                    )
                })}
            </Page>
        </div>
    )
}

Override

export function Page_Effects(): Override {
    return {
        gap: 0,
        effect(info) {
            const convertScale = transform([-1, 0, 1], [0.25, 1, 0.25])
            const scale = convertScale(info.normalizedOffset)
            const convertOpacity = transform([-1, 0, 1], [0, 1, 0])
            const opacity = convertOpacity(info.normalizedOffset)

            return { opacity, scale }
        },
    }
}

Note: The original effect (shown in the GIF) had a gap of -20, but a negative gap is not supported in the current version of the library.

17. Scroll

There’s no Override version of this example because you only need the Scroll tool to create it on the Canvas.

Code Component

const items = [0, 1, 2, 3, 4]
const height = 70
const padding = 10

export function Scrolling() {
    return (
        <Scroll radius={30} width={150} height={150} center>
            {items.map(index => {
                return (
                    <Frame
                        key={index}
                        height={height}
                        width={150}
                        radius={20}
                        top={(height + padding) * index}
                        background="#fff"
                    />
                )
            })}
        </Scroll>
    )
}

18. Scroll: Progress

This example uses the useMotionValue() and useTransform() hooks.

Code Component

export function ScrollProgress() {
    const items = [1, 2, 3, 4, 5, 6]
    const height = 70
    const padding = 10
    const itemsHeightTotal = items.length * height
    const totalPadding = (items.length - 1) * padding
    const contentHeight = itemsHeightTotal + totalPadding
    const scrollSize = 150
    const scrollDistance = -contentHeight + scrollSize

    const scrollY = useMotionValue(0)
    const width = useTransform(
        scrollY,
        [0, scrollDistance],
        ["calc(0% - 0px)", "calc(100% - 40px)"]
    )

    return (
        <div>
            <Scroll
                radius={30}
                contentOffsetY={scrollY}
                width={150}
                height={150}
                center={true}
            >
                {items.map(index => {
                    return (
                        <Frame
                            key={index}
                            height={height}
                            width={scrollSize}
                            radius={20}
                            top={(height + padding) * (index - 1)}
                            background="#fff"
                        />
                    )
                })}
            </Scroll>
            <Frame
                bottom={20}
                left={20}
                style={{ width }}
                height={6}
                background="#fff"
                radius={100}
            />
        </div>
    )
}

Override

const data = Data({
    widthBar: "calc(0% - 0px)",
})

export function Scroll_Progress_bar(): Override {
    return {
        width: data.widthBar,
    }
}

export function Scroll_Progress(props): Override {
    const contentHeight = props.children[0].props.children[0].props.height
    const scrollSize = props.height as number
    const scrollDistance = -contentHeight + scrollSize

    const scrollY = useMotionValue(0)
    const width = useTransform(
        scrollY,
        [0, scrollDistance],
        ["calc(0% - 40px)", "calc(100% - 40px)"]
    )
    return {
        contentOffsetY: scrollY,
        onScroll() {
            data.widthBar = width.get()
        },
    }
}

19. Scroll and Page

There’s no Override version of this example because you only need the Page and Scroll tools to recreate it.

Code Component

export function PageScroll() {
    return (
        <Page width={150} height={150} directionLock={true} radius={30} center>
            <Frame size={150} radius={30} background="#fff" />
            <Scroll
                width={150}
                height={150}
                radius={30}
                background="#fff"
                directionLock={true}
            >
                <Frame width={150} height={70} radius={20} background="#fff" />
                <Frame
                    top={80}
                    width={150}
                    height={70}
                    radius={20}
                    background="#fff"
                />
                <Frame
                    top={160}
                    width={150}
                    height={70}
                    radius={20}
                    background="#fff"
                />
                <Frame
                    top={240}
                    width={150}
                    height={70}
                    radius={20}
                    background="#fff"
                />
                <Frame
                    top={320}
                    width={150}
                    height={70}
                    radius={20}
                    background="#fff"
                />
            </Scroll>
            <Frame size={150} radius={30} background="#fff" />
        </Page>
    )
}

20. Scroll: Refresh

This example uses the useMotionValue() and useTransform() hooks. In the Override the non-hook version is used: motionValue().

Code Component

export function PullToRefresh() {
    const items = [1, 2, 3, 4, 5, 6]
    const height = 70
    const padding = 10
    const scrollSize = 150

    const scrollY = useMotionValue(0)
    const scale = useTransform(scrollY, [0, 100], [0, 1])
    const opacity = useTransform(scrollY, [0, 100], [0, 1])

    return (
        <div>
            <Scroll
                radius={30}
                contentOffsetY={scrollY}
                width={150}
                height={150}
                center={true}
            >
                {items.map(index => {
                    return (
                        <Frame
                            key={index}
                            height={height}
                            width={scrollSize}
                            radius={20}
                            top={(height + padding) * (index - 1)}
                            background="#fff"
                        />
                    )
                })}
            </Scroll>
            <Frame
                center={"x"}
                top={120}
                size={40}
                scale={scale}
                opacity={opacity}
                background="#fff"
                radius={100}
            />
        </div>
    )
}

Override

const scrollY = motionValue(0)

export function Scroll_Refresh_circle(): Override {
    const scale = useTransform(scrollY, [0, 100], [0, 1])
    const opacity = useTransform(scrollY, [0, 100], [0, 1])

    return {
        scale: scale,
        opacity: opacity,
    }
}

export function Scroll_Refresh(): Override {
    return {
        contentOffsetY: scrollY,
    }
}

21. Color: Arrays

Good to know: you can animate to Keyframes, to an array of values (here: three colors), and they will be spread out over the total duration (here: 2 seconds).

Code Component

export function ColorArrays() {
    return (
        <Frame
            size="100%"
            background="#40F"
            animate={{ background: ["#40F", "#09F", "#FA0"] }}
            transition={{ duration: 2, yoyo: Infinity }}
        >
            <Frame
                animate={{ rotate: [0, 180] }}
                transition={{ duration: 2, yoyo: Infinity }}
                size={150}
                borderRadius={30}
                background="#fff"
                center={true}
            />
        </Frame>
    )
}

Override

export function Color_Arrays_background(): Override {
    return {
        animate: { background: ["#50F", "#09F", "#FA0"] },
        transition: { duration: 2, yoyo: Infinity },
    }
}

export function Color_Arrays(): Override {
    return {
        animate: { rotate: [0, 180] },
        transition: { duration: 2, yoyo: Infinity },
    }
}

22. Color: Interpolation

Here the useMotionValue() and useTransform() hooks are used to interpolate between three colors. (In the Override: motionValue().)

Code Component

export function Interpolation() {
    const x = useMotionValue(0)
    const background = useTransform(x, [-100, 0, 100], ["#80F", "#40F", "#0BF"])

    return (
        <Frame size="100%" background={background}>
            <Frame
                size={150}
                radius={100}
                background="#fff"
                center={true}
                drag="x"
                dragConstraints={{
                    right: 0,
                    left: 0,
                }}
                x={x}
            />
        </Frame>
    )
}

Override

const x = motionValue(0)

export function Color_Interpolation_background(): Override {
    const background = useTransform(x, [-100, 0, 100], ["#80F", "#40F", "#0BF"])
    return {
        background: background,
    }
}

export function Color_Interpolation(): Override {
    return {
        drag: "x",
        dragConstraints: {
            right: 0,
            left: 0,
        },
        x: x,
    }
}

23. Color: Layered

The Code Component uses React’s useState() hook. In the Override version, I use a simple Boolean inside the Data object.

Code Component

export function Layered() {
    const [isHover, setHover] = useState(false)

    return (
        <Frame
            size="100%"
            background="#40F"
            animate={{ background: isHover ? "#09F" : "#40F" }}
            transition={{ duration: 0.5 }}
        >
            <Frame
                size={150}
                radius={30}
                background="#fff"
                center
                animate={{
                    scale: isHover ? 0.8 : 1,
                    rotate: isHover ? 90 : 0,
                }}
                onMouseEnter={() => {
                    setHover(true)
                }}
                onMouseLeave={() => {
                    setHover(false)
                }}
            />
        </Frame>
    )
}

Override

const data = Data({
    isHover: false,
})

export function Color_Layered_background(): Override {
    return {
        animate: { background: data.isHover ? "#09F" : "#40F" },
        transition: { duration: 0.5 },
    }
}

export function Color_Layered(): Override {
    return {
        animate: {
            scale: data.isHover ? 0.8 : 1,
            rotate: data.isHover ? 90 : 0,
        },
        onHoverStart() {
            data.isHover = true
        },
        onHoverEnd() {
            data.isHover = false
        },
    }
}

24. Cursor

You’re not limited to Framer’s events; you can also use React’s synthetic events like, e.g. onMouseMove.

Code Component

export function Cursor() {
    let [rotateX, setX] = useState(0)
    let [rotateY, setY] = useState(0)

    function handleMouse(event) {
        const x = event.pageX
        const y = event.pageY

        const xTransform = transform([0, 400], [-45, 45])
        const xNew = xTransform(x)
        const yTransform = transform([0, 400], [45, -45])
        const yNew = yTransform(y)

        setX(xNew)
        setY(yNew)
    }
    return (
        <Frame
            center
            size="100%"
            background="transparent"
            onMouseMove={handleMouse}
            style={{ perspective: 1000 }}
        >
            <Frame
                size={150}
                borderRadius={30}
                background="#fff"
                center={true}
                rotateX={rotateY}
                rotateY={rotateX}
            />
        </Frame>
    )
}

Override

const data = Data({
    rotateX: 0,
    rotateY: 0,
})

export function Cursor_background(): Override {
    const xTransform = transform([400, 800], [-45, 45]) // this Frame is 400 more to the right
    const yTransform = transform([0, 400], [45, -45])

    return {
        onMouseMove(event) {
            data.rotateX = yTransform(event.pageY)
            data.rotateY = xTransform(event.pageX)
        },
        style: {
            perspective: 1000,
        },
    }
}

export function Cursor(): Override {
    return {
        rotateX: data.rotateX,
        rotateY: data.rotateY,
    }
}

25. Grid

This is an example of how you can use the map() function to generate rows and columns. The Override version only contains a whileHover.

Code Component

const rows = [0, 1]
const cols = [0, 1]
const size = 70
const padding = 10

export function Grid() {
    return rows.map(rowIndex => {
        return cols.map(colIndex => {
            return (
                <Frame
                    center={true}
                    size={150}
                    background="transparent"
                    key={`${rowIndex} : ${colIndex}`}
                >
                    <Frame
                        size={size}
                        radius={20}
                        top={(size + padding) * rowIndex}
                        left={(size + padding) * colIndex}
                        background="#fff"
                        whileHover={{ scale: 0.8, opacity: 0.5 }}
                    />
                </Frame>
            )
        })
    })
}

Override

export function Grid(): Override {
    return {
        whileHover: { scale: 0.8, opacity: 0.5 },
    }
}

26. Variants

Here’s a simple example of an animation between two variants triggered by a whileHover.

With initial, you can define how the Frame should look when it first appears.

Code Component

export function Variants() {
    const variants = {
        variantA: { scale: 1, rotate: 90 },
        variantB: { scale: 1.25, rotate: 0 },
    }

    return (
        <Frame
            initial="variantA"
            whileHover="variantB"
            variants={variants}
            size={150}
            radius={30}
            center={true}
            background="#fff"
        />
    )
}

Override

export function Variants(): Override {
    const variants = {
        variantA: { scale: 1, rotate: 90 },
        variantB: { scale: 1.25, rotate: 0 },
    }
    return {
        initial: "variantA",
        whileHover: "variantB",
        variants: variants,
    }
}

27. Variants: Staggered

This animation uses the staggerChildren and delayChildren values that you can pass to the transition property.

Code Component

export function VariantsStagger() {
    const container = {
        hidden: { rotate: 90 },
        show: {
            top: "calc(50% - 75)",
            rotate: 0,
            transition: {
                staggerChildren: 0.1,
                delayChildren: 0.3,
            },
        },
    }

    const itemA = {
        hidden: { scale: 0, top: 100 },
        show: { scale: 1, top: 30 },
    }

    const itemB = {
        hidden: { scale: 0, top: 200 },
        show: { scale: 1, top: 80 },
    }

    const background = "#60F"

    return (
        <Frame
            variants={container}
            initial="hidden"
            animate="show"
            size={150}
            radius={30}
            background="#fff"
            center={true}
        >
            <Frame
                variants={itemA}
                size={40}
                left={30}
                radius={100}
                background={background}
            />
            <Frame
                variants={itemA}
                size={40}
                left={80}
                radius={100}
                background={background}
            />
            <Frame
                variants={itemB}
                size={40}
                left={30}
                radius={100}
                background={background}
            />
            <Frame
                variants={itemB}
                size={40}
                left={80}
                radius={100}
                background={background}
            />
        </Frame>
    )
}

Override

export function Variants_Stagger_childA(): Override {
    return {
        variants: {
            hidden: { scale: 0, top: 100 },
            show: { scale: 1, top: 30 },
        },
    }
}

export function Variants_Stagger_childB(): Override {
    return {
        variants: {
            hidden: { scale: 0, top: 200 },
            show: { scale: 1, top: 80 },
        },
    }
}

export function Variants_Stagger(): Override {
    const container = {
        hidden: { rotate: 90 },
        show: {
            rotate: 0,
            transition: {
                staggerChildren: 0.1,
                delayChildren: 0.3,
            },
        },
    }

    return {
        variants: container,
        initial: "hidden",
        animate: "show",
    }
}

28. Variants: Nesting

A child Frame doesn’t need any animate, or whileHover, etc. to be animated simultaneously. It just needs to have a set of variants with the same names as its parent.

Code Component

export function Variants() {
    const parent = {
        variantA: { scale: 1 },
        variantB: { scale: 1.25 },
    }

    const child = {
        variantA: { bottom: 0, right: 0, rotate: 0 },
        variantB: { top: 0, left: 0, rotate: 180 },
    }

    return (
        <Frame
            initial="variantA"
            whileHover="variantB"
            variants={parent}
            size={150}
            radius={30}
            center={true}
            background="#99F"
        >
            <Frame
                size={85}
                radius="20px 20px 30px 20px"
                background="#fff"
                bottom={0}
                right={0}
                variants={child}
                transition={{
                    type: "spring",
                    damping: 10,
                    mass: 0.2,
                    stiffness: 150,
                }}
            />
        </Frame>
    )
}

Override

export function Variants_Nested_child(): Override {
    return {
        variants: {
            variantA: { bottom: 0, right: 0, rotate: 0 },
            variantB: { top: 0, left: 0, rotate: 180 },
        },
        transition: {
            type: "spring",
            damping: 10,
            mass: 0.2,
            stiffness: 150,
        },
    }
}

export function Variants_Nested(): Override {
    return {
        initial: "variantA",
        whileHover: "variantB",
        variants: { variantA: { scale: 1 }, variantB: { scale: 1.25 } },
    }
}

29. Stack

No animation, just an example of how to create a <Stack> in code.

Code Component

export function Stacks() {
    return (
        <Stack
            size={150}
            center={true}
            direction="horizontal"
            radius={30}
            overflow="hidden"
            distribution="space-between"
        >
            <Frame width={70} height={150} background="#fff" radius={10} />
            <Frame width={70} height={150} background="#fff" radius={10} />
        </Stack>
    )
}

30. Stack: Animation

The Frames inside the Stack resize. Note that the distribution of the Stack is adjusted so that one Frame can push the other Frame to respectively the left or right side.

Code Component

export function StackAnimation() {
    const [align, setAlign] = useState<"start" | "end">("start")

    const variants = {
        normal: {
            width: 70,
        },
        expanded: {
            width: 150,
        },
    }

    const [animate, cycle] = useCycle("normal", "expanded")
    const [animate2, cycle2] = useCycle("normal", "expanded")

    return (
        <Stack
            size={150}
            center={true}
            direction="horizontal"
            radius={30}
            overflow="hidden"
            distribution={align}
        >
            <Frame
                width={70}
                height={150}
                background="#fff"
                radius={10}
                variants={variants}
                animate={animate}
                onTap={() => {
                    cycle()
                    setAlign("start")
                }}
            />
            <Frame
                width={70}
                height={150}
                background="#fff"
                radius={10}
                variants={variants}
                animate={animate2}
                onTap={() => {
                    cycle2()
                    setAlign("end")
                }}
            />
        </Stack>
    )
}

Override

const data = Data({
    stackDistribution: "start",
})

export function Stack_Animate_stack(): Override {
    return { distribution: data.stackDistribution }
}

export function Stack_Animate_left_frame(): Override {
    const [animate, cycle] = useCycle("normal", "expanded")

    return {
        variants: {
            normal: {
                width: 70,
            },
            expanded: {
                width: 150,
            },
        },
        animate: animate,
        onTap() {
            cycle()
            data.stackDistribution = "start"
        },
    }
}

export function Stack_Animate_right_frame(): Override {
    const [animate, cycle] = useCycle("normal", "expanded")

    return {
        variants: {
            normal: {
                width: 70,
            },
            expanded: {
                width: 150,
            },
        },
        animate: animate,
        onTap() {
            cycle()
            data.stackDistribution = "end"
        },
    }
}