framer / motion

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

[BUG] SVG animation fails when no initial value set #216

Closed mrmckeb closed 4 years ago

mrmckeb commented 4 years ago

Describe the bug When I don't define default/initial values as attributes, the component fails with the error:

Uncaught TypeError: Cannot read property 'red' of null

To Reproduce As an example, this component/animation fails. This should be a looping spinner.

const animation = {
  strokeDasharray: ['1px, 200px', '100px, 200px', '100px, 200px'],
  strokeDashoffset: [0, -15, -125],
  transition: { duration: 1.4, ease: 'linear' },
};

const Spinner: FC = (): ReactElement => {
  const controls = useAnimation();

  const handleAnimationComplete = useCallback(() => {
    controls.start(animation);
  }, [controls]);

  useEffect(() => {
    controls.start(animation);
  }, [controls]);

  return (
    <svg viewBox='22 22 44 44'>
      <Circle
        animate={controls}
        onAnimationComplete={handleAnimationComplete}
        cx='44'
        cy='44'
        r='20.2'
        fill='none'
        strokeWidth='3.6'
      />
    </svg>
  );
};

Expected behavior Modifing the circle to the below works, but this is the only way to resolve the issue. Setting the styles in CSS (with styled-components as an example) does not help.

      <Circle
        animate={controls}
        onAnimationComplete={handleAnimationComplete}
        strokeDasharray='80px, 200px'
        strokeDashoffset='0'
        cx='44'
        cy='44'
        r='20.2'
        fill='none'
        strokeWidth='3.6'
      />

Desktop (please complete the following information):

Additional context I had the same issue when trying to add a rotate animation to an SVG element. I could not find any workaround for this one, so any help would be greatly appreciated.

const Spinner: FC= (): ReactElement => {
  return (
    <motion.svg viewBox='22 22 44 44' animate={{ rotate: 100 }}>
      <Circle />
    </motion.svg>
  );
};

Other than this, I'm loving Framer Motion - amazing work!

mattgperry commented 4 years ago

Thanks for the detailed write-up! I'll take a look at this next week.

Glad you're enjoying it!

mrmckeb commented 4 years ago

Thanks @InventingWithMonster! Let me know if I can help at all.

artokun commented 4 years ago

Ran into the same issue. Also happens when using variants. In my case it happened when trying to animate the <g> tag inside an <svg />

Andarist commented 4 years ago

That's because initial~ values for SVGs are read from attributes here. That's certainly not that helpful - would be great to at least have a dev warning for this.

mrmckeb commented 4 years ago

Yes, @Andarist - I also guessed that (which is how I solved the issue for my use case). It would be great to see this improved in some way.

mattgperry commented 4 years ago

Currently working on this bug but if anyone runs into similar, you can always explicitly set an initial value via the initial prop.

const Spinner = () => (
    <motion.svg viewBox='22 22 44 44' initial={{ rotate: 0 }} animate={{ rotate: 100 }}>
      <Circle />
    </motion.svg>
  )
mrmckeb commented 4 years ago

Thanks @InventingWithMonster, that fix works - but if I use it with some properties, I get an error from react-dom:

Warning: Invalid DOM property `stroke-dasharray`. Did you mean `strokeDasharray`?
    in circle (created by RenderComponent)
    in RenderComponent (created by ForwardRef(MotionComponent))
    in ForwardRef(MotionComponent) (at Spinner.tsx:28)
    in svg (created by RenderComponent)
    in RenderComponent (created by ForwardRef(MotionComponent))
    in ForwardRef(MotionComponent) (at Spinner.tsx:27)
    in div (created by Spinner)
    in Spinner (at Spinner.tsx:24)
    in Spinner (at Spinner.stories.tsx:7)
    in ThemeProvider (at config.jsx:12)

In the DOM, I can see the attribute applied as stroke-dasharray;

mattgperry commented 4 years ago

initial={{ strokeDasharray: whatever }} comes out as that error?

mrmckeb commented 4 years ago

Yes, sadly. If you aren't seeing that, I can set up a demo for you.

jstcki commented 4 years ago

For the record, this is still happening in 1.2.6, although (if I understand correctly) it should have been fixed #221 and released in 1.2.5?

Example: https://codesandbox.io/s/confident-williamson-s09g1

… produces the same originally reported error Uncaught TypeError: Cannot read property 'red' of null

mattgperry commented 4 years ago

Ah yeah there may still be instances where this occurs. I'm tempted to default unreadable values to 0 - this would work well for the above but it'd break stuff like fill. I'll probably, in cases where we just can't find or infer an initial value, add a warning to ensure you set one.

jstcki commented 4 years ago

Yes, I think a meaningful error message would go a long way. I was super-confused about the Cannot read property 'red' of null part and only found out by trial and error how to fix it.

mrmckeb commented 4 years ago

Thanks for this!

amcdnl commented 4 years ago

I just ran into this today as well! I'm not sure 0 would be the expected here though. I ended up doing this:

const variants = {
  initial: ({ x, y }) => ({
    translateX: x,
    translateY: y,
    opacity: 0
  }),
  animate: ({ x, y }) => ({
    translateX: x,
    translateY: y,
    opacity: 1
  })
};