32. SVG

Most of these examples use motion.divs, but any HTML element can be animated with Framer Motion, including SVG paths.

Open Framer Motion version in CodeSandbox

Framer Motion

Notice that the pathLength motion value transforms the path’s opacity. The checkmark quickly fades in at the beginning of the pathLength animation.

export function FM32SVG() {
    const boxVariants = {
        checked: { scale: 1, backgroundColor: "rgba(255, 255, 255, 1)" },
        unchecked: { scale: 0.8, backgroundColor: "rgba(255, 255, 255 ,0.5)" },
    }

    const checkVariants = {
        checked: { pathLength: 0.9 },
        unchecked: { pathLength: 0 },
    }

    const [isChecked, setIsChecked] = React.useState(true)
    const pathLength = useMotionValue(0)
    const opacity = useTransform(pathLength, [0.05, 0.15], [0, 1])

    return (
        <Center>
            <motion.div
                style={{
                    width: 150,
                    height: 150,
                    borderRadius: 30,
                    backgroundColor: "rgba(255,255,255,0.5)",
                    cursor: "pointer",
                }}
                variants={boxVariants}
                initial={"checked"}
                animate={isChecked ? "checked" : "unchecked"}
                transition={{ type: "spring", stiffness: 300, damping: 20 }}
                onTap={() => setIsChecked(!isChecked)}
            >
                <svg
                    xmlns="http://www.w3.org/2000/svg"
                    width="150"
                    height="150"
                >
                    <motion.path
                        d="M38 74.707l24.647 24.646L116.5 45.5"
                        fill="transparent"
                        strokeWidth="20"
                        stroke="#A0D"
                        strokeLinecap="round"
                        variants={checkVariants}
                        style={{ pathLength: pathLength, opacity: opacity }}
                    />
                </svg>
            </motion.div>
        </Center>
    )
}

Code Component

Technically, this isn’t a 100% ‘only Framer library’ component. It isn’t yet possible to animate SVGs without motion, so the path is still a motion.path.

export function CC32SVG() {
    const boxVariants = {
        checked: { scale: 1, backgroundColor: "rgba(255, 255, 255, 1)" },
        unchecked: { scale: 0.8, backgroundColor: "rgba(255, 255, 255 ,0.5)" },
    }

    const checkVariants = {
        checked: { pathLength: 0.9 },
        unchecked: { pathLength: 0 },
    }

    const [isChecked, setIsChecked] = React.useState(true)
    const pathLength = useMotionValue(0)
    const opacity = useTransform(pathLength, [0.05, 0.15], [0, 1])

    return (
        <Frame
            // Visual & layout
            size={150}
            radius={30}
            backgroundColor="rgba(255,255,255,0.5)"
            center
            // Animation
            variants={boxVariants}
            animate={isChecked ? "checked" : "unchecked"}
            transition={{ type: "spring", stiffness: 300, damping: 20 }}
            onTap={() => setIsChecked(!isChecked)}
        >
            <svg xmlns="http://www.w3.org/2000/svg" width="150" height="150">
                <motion.path
                    // Visual
                    d="M38 74.707l24.647 24.646L116.5 45.5"
                    fill="transparent"
                    strokeWidth="20"
                    stroke="#A0D"
                    strokeLinecap="round"
                    // Animation
                    variants={checkVariants}
                    style={{ pathLength: pathLength, opacity: opacity }}
                />
            </svg>
        </Frame>
    )
}

Overrides

This also isn’t an ‘only Overrides’ example. I used Benjamin’s Animator package to animate the SVG.

The Animator component has a bunch of properties, and changing any of them will restart the animation. So simply changing its direction restarts the animation (in the correct direction).

const appState = Data({
    checked: true,
})

const scale = motionValue(0)

export function Frame(): Override {
    const boxVariants = {
        checked: { scale: 1, backgroundColor: "rgba(255, 255, 255, 1)" },
        unchecked: { scale: 0.8, backgroundColor: "rgba(255, 255, 255 ,0.5)" },
    }

    return {
        variants: boxVariants,
        animate: appState.checked ? "checked" : "unchecked",
        transition: { type: "spring", stiffness: 300, damping: 20 },
        onTap() {
            appState.checked = !appState.checked
        },
        scale: scale,
    }
}

export function Animator(): Override {
    return {
        direction: appState.checked ? "normal" : "reverse",
    }
}

export function AnimatorContainer(): Override {
    const opacity = useTransform(scale, [0.8, 0.805], [0, 1])
    return {
        opacity: opacity,
    }
}

Here the path’s opacity is also linked to the animation (as in the Motion example). But here it’s attached to the scale animation of the parent Frame. The path (very) quickly fades in at the start of the animation.


4 comments on “32. SVG”

  • Espen Staver says:

    Hi,
    for the code component version, how is it done when you have SVG’s in a separate, imported code component?

    • Tes Mat says:

      I don’t think you can, because those SVGs will contain normal paths (and not motion.paths).

      I suppose you would have to extract the path from each SVG (preferably directly from the .svg files, with something like this) and insert that in your code.

  • Espen Staver says:

    Thank you for the quick reply!

    I actually had my SVG’s (icons) in a code component in Framer, so I was able to add motion to the paths, but I still could not get it to work ( I am quite the novice both in React and Framer ).

    I was also just trying to animate a change in colour of the SVG stroke. After a lot of messing about, I found out that, since I was also changing the SVG’s stroke colour via property controls, it was those colour changes that interfered with the animation. The property control for colour will give off both HSL and RGB values it seems (If I start with HSL, changing alpha will result in RGB for example). So when I was attempting to animate with RGB values, that did not work.

    Now I don’t know how to safely “bypass” this behaviour, but as long as I know about it I can move on. Well, well 🙂

    • Tes Mat says:

      This error popped up when I was animation colors with Framer Motion:
      Error: Both colors must be hex/RGBA, OR both must be HSLA.
      That might be part of the problem.

      Also, when I did animate with only HSL(A) colors it cycled through a few colors (instead of animation straight from one color to the other)

Leave a Reply