Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...
granularclouds

Staggering children with react transition group

Recommended Posts

I'm in the process of converting a project from framer-motion to gsap owing to how much more powerful and performant the latter is ūüôā. I'm getting snagged on one area that is giving me a lot more trouble trying to pull off with gsap, though, unless I'm missing something very obvious.¬†

 

Here is a sandbox. 

 

What I'm trying to accomplish is to get a proper stagger working, on enter and exit whenever I change selection via the radio buttons. So that - on exit - the first of the two cards fades on, followed by the second, and then - on enter of the newly selected set of cards - the first new card fades in, followed by the second. 

 

I haven't found any such example detailing how one might accomplish this stagger-out/stagger-in action. 

 

Any help would be super appreciated - thanks!

Link to comment
Share on other sites

I haven't had a chance to fork this just yet, but at a very brief glance, it seems that your timeout is firing before your tweens are done animating.

 

Also, syntax note, duration should go inside the Tween object in GSAP v3+ (the old way still works, but isn't best practices).

 

gsap.from(node, 1.5, { ... }) -> gsap.from(node, { duration: 1.5, ... })

  • Like 3
Link to comment
Share on other sites

6 hours ago, OSUblake said:

All I can do is stagger the enter a little. The exit isn't firing.

The issue is that the in property in the <Transition> component has to be a boolean that defaults to false and now you're passing a string, so yeah the new element is being animated in when is mounted, but when the radio selection changes the elements are removed immediately, so basically is just acting as a conditional rendering logic, nothing more:

 

https://reactcommunity.org/react-transition-group/transition#Transition-prop-in

 

@granularclouds what you should do is create a way to loop through the object, then loop through the array of each object element and return the <Transition> component for each one. This code seems to do the trick:

return (
  <div className="App">
    <div>
      <div className="radio-container">
        /* ... */
      </div>
      <div className="card-container">
        <TransitionGroup>
          <div className="cards">
            {(() => {
              let test = [];
              for(let key in colors) {
                const elements = colors[key].map((item, i) => <Transition
                  in={key === color}
                  key={item.title}
                  onExit={exit}
                  onEntering={(el) => enter(el, i)}
                  mountOnEnter
                  unmountOnExit
                  appear
                  timeout={1500}
                >
                  <div className="card">
                    <h1> {item.title}</h1>
                    <h6>{item.subtitle}</h6>
                  </div>
                </Transition>
                );
                test = [...test, ...elements];
              }
              return test;
            })()}
          </div>
        </TransitionGroup>
      </div>
    </div>
  </div>
);

This could also be broken into smaller components in order to keep the code cleaner and easier to read. Also is recommended to match the timeout property in the <Transition> component to the duration of the GSAP instance, the timeout property tells transition group when to remove the elements and update the enter/exit state properties.

 

Happy Tweening!!!

  • Like 3
Link to comment
Share on other sites

Thanks for the replies all. 

 

And thanks so much @Rodrigo - I was lost for a sec looking at your code because I've never rendered JSX like that  - with the {() => {for(let x in y){... return z}}()} approach (interesting pattern to keep in my back pocket for other weird edge cases, thanks) - and while I am definitely curious if there's any possible way to get GSAP to work here using a more conventional map or similar, it definitely does work. Or at least for the enter animation - sandbox. Exit animation doesn't seem to be working. Am I missing something obvious? 

 

No sweat if the fix is laborious at all. I'd ideally like to stick with only one animation library going forward but I might have stumbled upon the sole case I've come across so far where framer motion looks like the way to go. Thanks again!

Link to comment
Share on other sites

2 hours ago, granularclouds said:

I'd ideally like to stick with only one animation library going forward but I might have stumbled upon the sole case I've come across so far where framer motion looks like the way to go. Thanks again!

 

Would you mind showing how you would solve this problem with framer motion? We're looking at different ways to make gsap easier to use in React.

 

 

  • Like 2
Link to comment
Share on other sites

1 hour ago, OSUblake said:

 

Would you mind showing how you would solve this problem with framer motion? We're looking at different ways to make gsap easier to use in React.

 

 

 

Sure! It's late so I don't have time to set up a sandbox for you to play around in - can quickly spin one up tomorrow, but below is the code I used:

 

const variants = {
  enter: {
    opacity: 1,
    transition: {
      staggerChildren: 0.2,
      delayChildren: 0.1,
    },
  },
  exit: {
    opacity: 0,
    transition: {
      staggerChildren: 0.1,
      staggerDirection: -1,
    },
  },
}

...

<AnimatePresence exitBeforeEnter initial={false}>
                <motion.div
                  variants={variants}
                  initial="exit"
                  animate="enter"
                  exit="exit"
                  className="AboutLinks__cards"
                  key={category.uid}
                >
                  {category.data.map((item, index) => {
                    return (
                      <Card
                        key={Object.values(item)[0].uid}
                        prefix={category.uid}
                        data={item}
                      />
                    )
                  })}
                </motion.div>
              </AnimatePresence>

 

The upside of framer motion is it's pretty much all declarative - everything set with props and objects. It's a pleasure to work with in React, but ultimately it's a lot more limited relative to GSAP, particularly for scroll-based functionality and anything SVG. Not as performant in my experience either. They're still building it though, and AnimateSharedLayout seems really cool - haven't used it for anything meaningful yet.

Link to comment
Share on other sites

3 hours ago, granularclouds said:

Exit animation doesn't seem to be working. Am I missing something obvious? 

Indeed you are :D:

const exit = (node) => {
  gsap.from(node, {
    duration: 1.5,
    delay: 0.5,
    opacity: 1,
    stagger: 0.5
  });
};

You're creating a from instance. That basically takes the elements from the values you're passing to it in the config object to the current ones the DOM element has. So basically this code is saying: "Take this cards and animate them from opacity: 1 to their current opacity". Turns out that their current opacity is also one, so GSAP is actually doing it's job, but this is animating the opacity from one to one, so visually there is no change at all.

 

This should work:

const exit = (node) => {
  gsap.to(node, {
    duration: 1.5,
    delay: 0.5,
    opacity: 0,
    stagger: 0.5
  });
};

Happy Tweening!!!

  • Like 2
Link to comment
Share on other sites

Thanks for the reply again! But I don't think that is making a difference - I understand what you are saying and I agree it makes sense, but the exit doesn't seem to be occurring until the enter is all the way complete. And no fade out.

 

I'm pretty sure the code is exactly how you specified. 

Link to comment
Share on other sites

1 hour ago, granularclouds said:

The upside of framer motion is it's pretty much all declarative - everything set with props and objects. It's a pleasure to work with in React, but ultimately it's a lot more limited relative to GSAP, particularly for scroll-based functionality and anything SVG. Not as performant in my experience either. They're still building it though, and AnimateSharedLayout seems really cool - haven't used it for anything meaningful yet.

 

Thanks for the feedback! Would you be interested in providing more feedback in the next couple of weeks? I'd like to set up some type of forum/channel to get feedback from React devs.

 

  • Like 4
Link to comment
Share on other sites

@granularclouds Be more careful with your syntax :D

 

<Transition
    in={key === color}
   onExt={exit} // onExit <--!!!!!!!
    onEntering={(el) => enter(el, i)}
    mountOnEnter
    unmountOnExit
    appear
    timeout={1500}
>

 

Happy Tweening!!!

  • Like 3
Link to comment
Share on other sites

Hi @Rodrigo is timeout the only way to let it know when to remove the elements? Seems a little clunky. I saw addEndListener with a done callback, but that seems to only when entering, or am I missing something?

 

 

  • Like 1
Link to comment
Share on other sites

42 minutes ago, Rodrigo said:

@granularclouds Be more careful with your syntax :D

 

<Transition
    in={key === color}
   onExt={exit} // onExit <--!!!!!!!
    onEntering={(el) => enter(el, i)}
    mountOnEnter
    unmountOnExit
    appear
    timeout={1500}
>

 

Happy Tweening!!!

 

Ah, sorry - must have typo'd at the last second before logging off last night. It's still not quite working - animations are happening sequentially. Even if I get rid of one or the other of the delay and duration in the exit function, the exiting and entering components are still on screen at the same time...

Link to comment
Share on other sites

10 hours ago, OSUblake said:

 

Thanks for the feedback! Would you be interested in providing more feedback in the next couple of weeks? I'd like to set up some type of forum/channel to get feedback from React devs.

 

 

Absolutely, would love to help.

Link to comment
Share on other sites

The done callback should be able to do that on both cases enter/exit. Keep in mind that enter and exit are different things, similar to how GSAP handles display: "none"/"block".

 

The real thing is that done and timeout are just used to update the state of the <Transition> component, nothing more. Depending on a specific state property the component is either rendered or not. I don't want to get into more specifics because, IMHO we don't need even more confusion about the usage of GSAP in a React app around here :D

 

Here you can see the render method of the <Transition> component (a class component maintained by the React team, go figure ;)):

https://github.com/reactjs/react-transition-group/blob/master/src/Transition.js#L342-L378

 

At the top you find the conditional rendering logic for it:

const status = this.state.status

if (status === UNMOUNTED) {
  return null
}

The status property changes between this values depending on the stage of the transition:

export const UNMOUNTED = 'unmounted'
export const EXITED = 'exited'
export const ENTERING = 'entering'
export const ENTERED = 'entered'
export const EXITING = 'exiting'

If you follow the logic in the componentDidUpdate hook and the associated callbacks you'll see that this isn't exactly setting up a nuclear reactor, is quite simple actually.

 

In this sample you can check it in dev tools, is a bit outdated but the mechanics of it are pretty much the same. If you add a new element is mounted then animated in. If you remove an element is animated out and then unmounted from the DOM:

https://stackblitz.com/edit/gsap-react-transition-group-list?file=transition-card.js

 

Finally I don't know all the ins and outs of transition group but is a great package and certainly quite useful in some specific cases.

  • Like 1
Link to comment
Share on other sites

7 minutes ago, granularclouds said:

Even if I get rid of one or the other of the delay and duration in the exit function, the exiting and entering components are still on screen at the same time...

That happens because all of them are in the DOM at the same time, you can solve that by tinkering with the position styles in order to take one or all  of them from document flow or use the switch transition component:

 

https://reactcommunity.org/react-transition-group/switch-transition

 

Happy Tweening!!!

  • Like 1
Link to comment
Share on other sites

3 hours ago, Rodrigo said:

That happens because all of them are in the DOM at the same time, you can solve that by tinkering with the position styles in order to take one or all  of them from document flow or use the switch transition component:

 

https://reactcommunity.org/react-transition-group/switch-transition

 

Happy Tweening!!!

Thanks Rodrigo, I'll definitely look into Switch Transition. That indeed looks like the component I think I would need for this. 

 

16 hours ago, OSUblake said:

 

Would you mind showing how you would solve this problem with framer motion? We're looking at different ways to make gsap easier to use in React.

 

 

 

Here is a sandbox with a framer motion implementation. Not an ideal demo of either, rather rough in fact, and the GSAP one is still broken - will have to dig into that switch RTG component later today. 

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
√ó