Jump to content
Search Community

Conditional verticalLoop in React

broflovski test
Moderator Tag

Go to solution Solved by Rodrigo,

Recommended Posts

Hello, I'm struggling to solve a problem with a verticalLoop animation on columns in my web layout. Any suggestions or code would be appreciated.

 

Here's the demo: https://stackblitz.com/edit/gsap-react-basic-f48716-cphkak?file=src%2FApp.tsx

 

I want to apply the verticalLoop animation to the columns. There are usually two columns, but sometimes it appears as one column due to window size. I've adjusted the layout using flex-wrap-reverse in tailwindCSS.


The issue is that the animation doesn't work correctly after resizing the window. Specifically, I want two verticalLoop animations for each column when they are side by side, and one verticalLoop animation when they appear as a single column due to the flex-wrap CSS property.

 

To handle this, I've created an isFlexWrapped state to conditionally run the animations. I also have a getIsFlexWrap function to detect the column layout on window resize. However, after resizing, the animation doesn't work as expected.

 

The intended animation behavior is as follows:

- When there are two columns, the first column's animation speed is 1, and the second column's animation speed is 0.9.

- When there's one column, the animation speed for the entire column is 1.

- During window resizing, the animation should either pause or reset to the first position (progress 0).
- After resizing the window and the column layout changes (from 2 to 1 or 1 to 2 columns), the appropriate animation should run.


I'm not sure what I'm missing or if I've used the verticalLoop animation or the useLayoutEffect hook incorrectly. Any hints or guidance would be greatly appreciated. Thank you for your assistance.

Link to comment
Share on other sites

  • Solution

Hi @broflovski and welcome to the GreenSock forums!

 

Thanks for being a Club GreenSock member and supporting GreenSock! 💚

 

You are using React and when working with React you should always use GSAP Context:

https://greensock.com/docs/v3/GSAP/gsap.context()

 

In this case though it would be better to use GSAP MatchMedia, since MatchMedia is a responsive wrapper for GSAP Context:

https://greensock.com/docs/v3/GSAP/gsap.matchMedia()

 

In this page you can find articles that tell you how to use GSAP Context in React:

In the case of MatchMedia you can use it like this

useLayoutEffect(() => {
  const mm = gsap.matchMedia();
  
  mm.add("(min-width: 768px)", () => {
    // create your vertical loops here
  });
  
  // Add other breakpoints to your MatchMedia instance
  // as you see fit
  
  return () => mm.revert();
}, []);

Hopefully this helps.

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

Thank you for your prompt response and the suggestion! I was unaware that I could use matchMedia in this scenario.

 

I've modified the code accordingly, and the link remains unchanged from before. 

 

  useLayoutEffect(() => {
    const mm = gsap.matchMedia();
    const selector = gsap.utils.selector('#container');
    const firstColumnItems = selector('#column-01 > *');
    const secondColumnItems = selector('#column-02 > *');
    const [item] = selector('#column-01 > div:first-child');
    const paddingBottom = +gsap.getProperty(item, 'marginBottom');
    const wholeItems = [...secondColumnItems, ...firstColumnItems];

    mm.add(
      {
        isOneColumn: '(max-width: 727px)',
        isTwoColumn: '(min-width: 728px)',
      },
      (context) => {
        const { conditions } = context;

        if (conditions?.isOneColumn) {
          const wholeItemsLoop = verticalLoop(wholeItems, {
            paused: true,
            repeat: -1,
            speed: 1,
            paddingBottom,
            reversed: false,
          });

          wholeItemsLoop.play();

          return () => wholeItemsLoop.kill();
        }

        if (conditions.isTwoColumn) {
          const firstColumnLoop = verticalLoop(firstColumnItems, {
            paused: true,
            repeat: -1,
            speed: 0.9,
            paddingBottom,
            reversed: false,
          });

          const secondColumnLoop = verticalLoop(secondColumnItems, {
            paused: true,
            repeat: -1,
            speed: 1,
            paddingBottom,
            reversed: false,
          });

          firstColumnLoop.play();
          secondColumnLoop.play();

          return () => {
            firstColumnLoop.kill();
            secondColumnLoop.kill();
          };
        }
      },
      root
    );

    return () => mm.revert();
  }, []);

 

 

However, this resulted in the following error:

Error in /turbo_modules/gsap@3.12.2/dist/gsap.js (1325:29)
Maximum call stack size exceeded

 

Interestingly, when I comment out the repeat: -1, it works as intended. However, it only runs once, which is not the behavior I'm aiming for. Do you think I might have misused the matchMedia method? Your assistance is greatly appreciated. Thank you.

Edited by broflovski
Using Code instead of copy and paste
Link to comment
Share on other sites

It's pretty tough to troubleshoot without a minimal demo - the issue could be caused by CSS, markup, a third party library, your browser, an external script that's totally unrelated to GSAP, etc. Would you please provide a very simple CodePen or Stackblitz that demonstrates the issue? 

 

Please don't include your whole project. Just some colored <div> elements and the GSAP code is best. See if you can recreate the issue with as few dependancies as possible. If not, incrementally add code bit by bit until it breaks. Usually people solve their own issues during this process! If not, then at least we have a reduced test case which greatly increases your chances of getting a relevant answer.

 

Here's a starter CodePen that loads all the plugins. Just click "fork" at the bottom right and make your minimal demo

See the Pen aYYOdN by GreenSock (@GreenSock) on CodePen

 

Using a framework/library like React, Vue, Next, etc.? 

CodePen isn't always ideal for these tools, so here are some Stackblitz starter templates that you can fork and import the gsap-trial NPM package for using any of the bonus plugins: 

 

Please share the StackBlitz link directly to the file in question (where you've put the GSAP code) so we don't need to hunt through all the files. 

 

Once we see an isolated demo, we'll do our best to jump in and help with your GSAP-specific questions. 

Link to comment
Share on other sites

Alright, I've identified the reason for the error. It arose from my use of the revert method within <StrictMode>. Strangely, the revert method didn't function correctly, but switching to the kill method in the clean-up function of the useLayoutEffect hook resolved the issue.

 

I'm still uncertain about the fundamental differences between these two methods and how <StrictMode> operates... but the solution works. I wanted to update you on this progress and express my gratitude for your assistance in resolving the problem. Thank you.

Link to comment
Share on other sites

Hi,

 

I strongly recommend you keep using GSAP Context/MatchMedia when working with React/Next. What you could do is to just kill the loop instances by hand in the cleanup phase:

useLayoutEffect(() => {
  const ctx = gsap.context(() => {});
  const loopOne = verticalLoop(/*...*/);  
  const loopTwo = verticalLoop(/*...*/);
  
  return () => {
    ctx.revert();
    loopOne.kill();
    loopTwo.kill();
  };
}, []);

I couldn't tell you exactly what the issue is here, but is quite specific to some minor update in React or something in React's build process and a specific part of the loop function, nothing more.

 

React 18 runs in strict mode locally by default which causes your useEffect() and useLayoutEffect() to get called TWICE.

In GSAP 3.11, we introduced a new gsap.context() feature that helps make animation cleanup a breeze. All you need to do is wrap your code in a context call. All GSAP animations and ScrollTriggers created within the function get collected up in that context so that you can easily revert() ALL of them at once.

Here's the structure:

// typically it's best to useLayoutEffect() instead of useEffect() to have React render the initial state properly from the very start.
useLayoutEffect(() => {
  let ctx = gsap.context(() => {
    // all your GSAP animation code here
  });
  return () => ctx.revert(); // <- cleanup!
}, []);

This pattern follows React's best practices, and one of the React team members chimed in here if you'd like more background.

We strongly recommend reading the React information we've put together at:

https://gsap.com/resources/React/

 

Hopefully this helps.

Happy Tweening!

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...