Jump to content
Search Community

Animate by toggling state

henryb test
Moderator Tag

Go to solution Solved by Rodrigo,

Recommended Posts

Hello,

I'm building a page (in NextJS App router) that features a toggle group that allows the user to toggle the grid layout from 4 columns to 6 columns on click. I would like to run an animation when state updates. The animation will run in the following sequence:
1. Fade out the grid items
2. Update the number of columns in the grid layout
3. Fade back in the grid items

Stackblitz

However I am facing the following issues:
1. If the timeline is not paused then the state toggles when the page loads (grid is 4 columns momentarily then switches to 6 columns). This would indicate to me that the timeline runs. However, if the timeline is paused when the component mounts, then I am subsequently unable to run the timeline animation by toggling the state.
2. I can console.log that the state is being updated in the component, but despite the state being in dependency of useGSAP, the timeline does not appear to run. I am unclear why.
NB: My reason for wanting to use state to run the animation, is that the state would also be used to toggle the sizes values passed to the Image component when the grid changes. And also because I don't think the animation would work with a simple reverse(). (The grid items must be visible when component mounts and then the animation must go, -> to autoAlpha: 0, -> change layout, -> to autoAlpha: 1 and that doesn't really work in reverse unless I am completely missing something).

I have a feeling this is quite basic, but despite reading over the docs and reviewing other examples, I haven't been able to get this working. Very grateful for any input anyone may have. Many thanks.
Henry

P.S. An external website example, in case my description wasn't clear.

Link to comment
Share on other sites

  • Solution

Hi,

 

There are two issues in your demo, first the card selector is returning an empty array, because there is nothing with a class card anywhere in your code:

const targets = gsap.utils.toArray('.card'); // -> [] EMPTY ARRAY

Second, you're creating the timeline on each state update, just create it once and restart it based on the state

useLayoutEffect(() => {
  console.log(gridSizeLarge);
  timeline.current?.restart();
}, [gridSizeLarge]);

useGSAP(
  () => {
    const targets = gsap.utils.toArray('.card');
    timeline.current = gsap
      .timeline({ paused: true })
      .to(targets, { autoAlpha: 0, duration: 0.2, ease: 'power4.out' })
      .to(
      targets,
      {
        autoAlpha: 1,
        duration: 0.2,
        ease: 'power4.out',
        onStart: () => {
          container.current &&
            container.current.classList.toggle('grid-cols-4');
          container.current &&
            container.current.classList.toggle('grid-cols-6');
        },
      },
      '>'
    );
  },
  { scope: container }
);

No IDK where the card class should go, so I took my best guess and came up with this:

https://stackblitz.com/edit/stackblitz-starters-y4hj8o?file=components%2FProductGrid.tsx,components%2FProductCard.tsx

 

Hopefully this helps.

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

Thanks so much, Rodrigo. That is exactly what I am looking for. P.S. Nice touch with the stagger on the cards ;)

I think this is starting to click for me, in regards to setting up a timeline and then calling methods like restart() on the timeline, and to avoid the mistake of just recreating the timeline.

Link to comment
Share on other sites

Oh, I just had one more related question, if you have time; in this scenario should I have created a contextSafe function to handle the onClick?

 

I get a little confused about whether this needs to be made contextSafe since it is animating after the timeline has been created, from an onClick.

Link to comment
Share on other sites

3 hours ago, henryb said:

in this scenario should I have created a contextSafe function to handle the onClick?

Nope, no need for that. The reason is that you are restarting a Timeline that already is scoped inside the useGSAP hook and that will be reverted during cleanup when/if the component unmounts. Something like this:
https://stackblitz.com/edit/gsap-react-basic-f48716

 

You would need to use contexSafe if you were doing something like this:

const myClickHandler = () => {
  gsap.to(".element", { rotation: "+=180" });
};

In that case that animation is not being reverted nor cleaned up, because is not inside a GSAP Context instance, for that you use contextSafe:

const { contextSafe } = useGSAP(() => {}, {
  scope: myRef,
});

const myClickHandler = contextSafe(() => {
  gsap.to(".element", { rotation: "+=180" });
});

Finally that selector being used is now using the scope passed in the useGSAP hook.

 

Hopefully this clear things up.

Happy Tweening!

  • 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.
×
×
  • Create New...