henryb Posted May 14 Share Posted May 14 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 More sharing options...
Solution Rodrigo Posted May 14 Solution Share Posted May 14 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! 1 Link to comment Share on other sites More sharing options...
henryb Posted May 15 Author Share Posted May 15 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 More sharing options...
henryb Posted May 15 Author Share Posted May 15 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 More sharing options...
Rodrigo Posted May 15 Share Posted May 15 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! 1 Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now