framer / motion

Open source, production-ready animation and gesture library for React
https://framer.com/motion
MIT License
23.32k stars 776 forks source link

[BUG] Trying to manipulate SVG "d" values causes error #451

Closed Mellis84 closed 3 years ago

Mellis84 commented 4 years ago

I am wanting to use the same functionality to Poses SVG morphing, I try something like:

const boxVariants = {
  initial: {
    fill: "pink",
    d:
      "M510,255c0-20.4-17.85-38.25-38.25-38.25H331.5L204,12.75h-51l63.75,204H76.5l-38.25-51H0L25.5,255L0,344.25h38.25 l38.25-51h140.25l-63.75,204h51l127.5-204h140.25C492.15,293.25,510,275.4,510,255z",
  },
  hover: {
    fill: "pink",
    d:
      "M255,0C114.75,0,0,114.75,0,255s114.75,255,255,255s255-114.75,255-255S395.25,0,255,0z",
  },
}

And I get an error saying: Error: Complex values 'M255,0C114.75,0,0,114.75,0,255s114.75,255,255,255s255-114.75,255-255S395.25,0,255,0z' and 'M510,255c0-20.4-17.85-38.25-38.25-38.25H331.5L204,12.75h-51l63.75,204H76.5l-38.25-51H0L25.5,255L0,344.25h38.25 l38.25-51h140.25l-63.75,204h51l127.5-204h140.25C492.15,293.25,510,275.4,510,255z' too different to mix. Ensure all colors are of the same type. at invariant (hey-listen.es.js:11)

Can Framer Motion support SVG morphing yet?

Cheers!

zambo commented 4 years ago

Same here. Hover works fine, but I get an error on open.

          variants={{
            closed: { d: "M8.9,8.4h14.1" },
            open: { d: "M8.888,8.888,23.112,23.112" },
            hover: { d: "M5.9,8.4h20.1" },
          }}
         variants={{
            closed: { d: "M8.9,23.6h14.1" },
            open: { d: "M8.888,23.112,23.112,8.888" },
            hover: { d: "M5.9,23.6h20.1" },
          }}
larsvankleef commented 4 years ago

Same here. But i don't think it is really a bug. Can't find a thing about it in the documentation 😿

zambo commented 4 years ago

As far as I understand, it doesn't morph the SVG, it just manipulate its positions. So, maybe when one of the variants has different paths it doesn't know what to do.

In my case, I've changed every h inside my SVG to l and it's working now.

variants={{
  closed: { d: " M8.942, 8.421 L 23.058, 8.42" },
  open: { d: "M 8.888, 8.888, L 23.112 ,23.112" },
  hover: { d: "M 5.942, 8.421 L 26.058, 8.421" },
}}

This is just one line of a hamburger menu, so it's quite easy to change it by hand. Don't know about more complex SVGs. Illustrator, figma, xd and sketch each exported it a little bit different.

Here are the svg path properties for reference:

M = moveto L = lineto H = horizontal lineto V = vertical lineto C = curveto S = smooth curveto Q = quadratic Bézier curve T = smooth quadratic Bézier curveto A = elliptical Arc Z = closepath

Regaddi commented 3 years ago

I'm experiencing this issue when changing the amount of points inside of a <motion.path>. Here's a CodeSandbox demonstrating the issue: https://codesandbox.io/s/wizardly-feather-qqv3q?file=/src/App.js Click on the SVG and you should see the error: image

kevinwolfcr commented 3 years ago

I created a custom hook to solve this by using flubber, useMotionValue and animate. Check it out:

function useSVGMorph(
  d: string,
  config: Parameters<typeof animate>[2] = {}
): MotionValue<string> {
  const value = useMotionValue<string>(d);

  React.useEffect(() => {
    const interpolator = interpolate(value.get(), d);

    animate(0, 1, {
      ...config,
      onUpdate: (progress) => value.set(interpolator(progress))
    });
  }, [config, d, value]);

  return value;
}

You can just pass the desired d as the first argument and other options (like duration) as the second parameter, here is an example:

import {
  animate,
  motion,
  MotionValue,
  useCycle,
  useMotionValue
} from "framer-motion";
import * as React from "react";
import { interpolate } from "flubber";

const shapes = {
  rectangle: "M0,25 L100,25 L100,75 L0,75Z",
  triangle: "M0,100 L50,0 L100,100Z",
  square: "M0,0 L100,0 L100,100 L0,100Z"
};

function useSVGMorph(
  d: string,
  config: Parameters<typeof animate>[2] = {}
): MotionValue<string> {
  const value = useMotionValue<string>(d);

  React.useEffect(() => {
    const interpolator = interpolate(value.get(), d);

    animate(0, 1, {
      ...config,
      onUpdate: (progress) => value.set(interpolator(progress))
    });
  }, [config, d, value]);

  return value;
}

export default function App() {
  const [shape, cycleShape] = useCycle("rectangle", "triangle", "square");

  const d = useSVGMorph(shapes[shape as keyof typeof shapes], {
    duration: 0.5
  });

  return (
    <div>
      <svg width={100} height={100} viewBox="0 0  100 100">
        <motion.path d={d} />
      </svg>
      <p>
        <button onClick={() => cycleShape()}>TOGGLE</button>
      </p>
    </div>
  );
}

And a demo sandbox

mattgperry commented 3 years ago

The "Ensure colors are of the same type" is unexpected. That should read the two values should contain the same number of numbers and colors (that are of the same type). As others have pointed out if the path changes too much you need a morpher.