framer / motion

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

[BUG] AnimatePresence + nested motion & before/afterChildren: TypeError: Failed to execute 'getComputedStyle' + enter/exit triggered multiple times #2387

Open giliamverheide opened 8 months ago

giliamverheide commented 8 months ago

1. Describe the bug

Inside AnimatePresence, I have nested motion 3 levels deep, with a structure something like this (see codesandbox):

<AnimatePresence>
  {show ? (
    <motion.div>  // outer container, level 1
      <motion.div> // button container, level 2
        <motion.div> // button wrapper, level 3
          ...
  ) : null}
</AnimatePresence>

Each motion element uses variants to set either a 'visible' (animate) or 'hidden' (exit) state. On enter, each motion element animates after its parent has animated (1 → 2 → 3) via beforeChildren. On exit, each motion parent animates after its child has animated (3 → 2 → 1) via afterChildren.

Issue 1 - Animating width causes TypeError:

If I click the button that toggles between the 'hidden' and 'visible' state twice in a row, before any of the animations have completed, the error TypeError: Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'. appears.

Additionally, this will now only trigger the enter (visible) animation for all motion elements no matter the state.

https://github.com/framer/motion/assets/15910702/688e72b6-94fe-4496-857f-20f42d89e215

The TypeError specifically seems to be caused by animating the width from/to "auto" - "72px" AND also using when: 'afterChildren' on the exit/hidden variant. If we were to animate just the opacity, we CAN use when and the TypeError never occurs (see codesandbox).

// level 2
const buttonContainer: Variants = {
  visible: {
    width: "auto", // Causes TypeError: Failed to execute 'getComputedStyle'
    // opacity: 1, // Does not cause error
    transition: {
      when: "beforeChildren",
      duration: 0.6,
    },
  },
  hidden: {
    width: "72px", // Causes TypeError: Failed to execute 'getComputedStyle'
    // opacity: 0, // Does not cause error
    transition: {
      bounce: 0,
      when: "afterChildren", // Removing this line will allow animating from auto <--> '72px' without causing TypeError
      duration: 0.6,
    },
  },
}

Issue 2 - Enter/exit animations are triggered (started) twice

When the 'hidden' state is triggered, level 3 (the most inner child) starts animating. If I toggle the state back to 'visible' before any animations have completed, those enter animations will trigger twice.

https://github.com/framer/motion/assets/15910702/9d92e197-7716-491a-b864-10b79c2b78ff

For example, I set the state to 'hidden' and immediately switch it back to 'visible' before any animations have completed, then the animation sequence looks like this:

---------- HIDE ----------
1. outerContainer start
2. buttonContainer start
3. buttonWrapper start
---------- SHOW ----------
1. outerContainer start
2. buttonContainer start
3. buttonWrapper start

1. outerContainer start
2. buttonContainer start
3. buttonWrapper start

3. buttonWrapper complete - (visible)
2. buttonContainer complete - (visible)
1. outerContainer complete - (visible)

Or visa versa, I set the state to 'visible' and immediately switch it back to 'hidden', right after the first element (out of 3) has started animating, then the animations sequence looks like this:

---------- SHOW ----------
1. outerContainer start
---------- HIDE ----------
1. outerContainer start
2. buttonContainer start
3. buttonWrapper start
2. buttonContainer start
3. buttonWrapper complete - (hidden)
3. buttonWrapper start
2. buttonContainer complete - (hidden)
3. buttonWrapper complete - (visible)
2. buttonContainer complete - (visible)
1. outerContainer complete - (visible)
1. outerContainer complete - (hidden)

https://github.com/framer/motion/assets/15910702/9471b719-0385-41ab-bfce-2974133b9f86

2. CodeSandbox reproduction of the bug

https://codesandbox.io/p/sandbox/framer-motion-nested-mm9r8k?file=%2Fsrc%2Fpages%2Findex.tsx%3A11%2C21

3. Expected behavior

When changing the variant / toggling between 2 animation states, I expect the animation to stop and reverse and not start over (multiple times).

In other words, imagine we animated the opacity from 0 → 1 over 1 second linearly. After 0.7s, when the opacity is 0.7, I change/toggle the variant back to its previous value, I expect the opacity to go from 0.7 → 0 in 0.7s, the exact reverse of the animation thus far.

And when we take the above scenario of several nested motion components that animate after each other; if I start the animation (top level starts animating) and toggle the state back to the initial/previous state before the animation has completed, I don't expect the nested component (level 2) to start animating at all. Rather, the first animation should reverse / animate to the initial state.

4. Environment details

Google Chrome

"next": "^13.5.6", "react": "18.2.0", "react-dom": "18.2.0", "framer-motion": "^10.16.4"

KevinEdry commented 5 months ago

Issue 2 occurs for me as well. Does anyone have an idea of what is the issue?

CarlosSimon02 commented 4 months ago

I'm facing the same problem mentioned in issue 2. If I click the button before the transition is done, the first animation happens twice. The only fix I found is to use useAnimate()

<AnimatePresence initial={false}>
  {currentTheme === ThemeType.Light ? (
    <AnimatedMoonIcon key="moon" {...iconAnimateProps} />
  ) : (
    <AnimatedSunIcon key="sun" {...iconAnimateProps} />
  )}
</AnimatePresence>

Uploading 2024-02-10 14-29-19.mp4…