Jump to content
Search Community

Is stagger animations possible with scrollTrigger in GSAP (ReactJS)

Figo test
Moderator Tag

Go to solution Solved by Figo,

Recommended Posts

I have tried to different methods for creating a stagger animation with scrollTrigger, is this possible thought with GSAP (in ReactJS) ?

 

What I'm trying to achieve, is to have a headline animate, followed by a second headline,  follow by p text once the user scrolls into the trigger.

In this method, GSAP ignores "delay":

LINK TO CODESANDBOX => https://codesandbox.io/s/winter-bush-z0qbu?file=/src/App.js

 

Here is another method where I use staggerFrom, but as soon as I add scrollTrigger to the animation it doesn't work: 

LINK TO CODESANDBOX (without scrollTrigger) => https://codesandbox.io/s/great-goldberg-ivkf6?file=/src/App.js

LINK TO CODESANDBOX (with scrollTrigger) =>https://codesandbox.io/s/icy-bash-180e8?file=/src/App.js

 

Any advice would be super appreciated

Link to comment
Share on other sites

I don't think it was related to that edge case in the other thread (that'd only occur if you had a "restart" in your toggleActions and a stagger and delay). 

 

I noticed a few things:

  1. You're using invalid start values. It's supposed to be a space-delimited value where the first one relates to the trigger, and the second to the scroller (viewport). 
    // BAD
    start: "20%"
    
    // GOOD
    start: "20% bottom" // when 20% from the top of the trigger hits the bottom of the viewport
  2. You were missing the "." in the class name selector text:
    // BAD
    trigger: "trigger-container"
    
    // GOOD
    trigger: ".trigger-container"

    Beware you may need to use refs in React, but I'm not sure (I'm not a React guy). 

  3. You're creating 3 separate animations, each in a useEffect(), but you can just create a single staggered animation like:
    useEffect(() => {
      gsap.from([headlineFirst, headlineSecond, contentP], {
        y: 49,
        duration: 1,
        stagger: 0.2,
        scrollTrigger: {
          trigger: ".trigger-container",
          start: "20% 100%",
          markers: true,
          toggleActions: "restart none none none"
        }
      });
    }, []);

     

This makes them always come in staggered in the same way, but if you prefer to heave each individual element only start animating when it enters the viewport (which would sorta look like staggering if you scroll relatively slowly), then just use a .forEach() and create an animation for each one, as described here.

 

Happy tweening!

  • Like 2
Link to comment
Share on other sites

James you are a legend! I'm 99% there. If you don't mind me asking you, in that example in number 3, if I had to give the "contentP", different values for the animation, would it be possible? Still the same trigger

 

Here is an example but running into a syntax error:
 

useEffect(() => {
  gsap.from([headlineFirst, headlineSecond, contentP], {
    y: 49,
    duration: 1,
    stagger: 0.2,
    scrollTrigger: {
      trigger: ".trigger-container",
      start: "20% 100%",
      markers: true,
      toggleActions: "restart none none none"
    }.from(contentP, 1, { y: 20, opacity: 0 }
  })
}, []);

 

Link to comment
Share on other sites

I can probably just write the animation twice for the other div to have it's own values, but would be neat if I could combine them.

useEffect(() => {
    gsap.from([headlineFirst, headlineSecond, contentP], {
      y: 49,
      duration: 1,
      stagger: 0.2,
      scrollTrigger: {
        trigger: ".trigger-container",
        start: "20% 100%",
        markers: true,
        toggleActions: "play none none none"
      }
    });
  }, []);

  useEffect(() => {
    gsap.from(contentP, {
      y: 20,
      duration: 1,
      opacity:0,
      stagger: 0.2,
      scrollTrigger: {
        trigger: ".trigger-container",
        start: "20% 100%",
        markers: true,
        toggleActions: "play none none none"
      }
    });
  }, []);

 

Link to comment
Share on other sites

Technically you could by using a function-based value, but it's probably simplest to just use two tweens. It's totally up to you. 

 

gsap.from([headlineFirst, headlineSecond, contentP], {
  y: (i, target) => {
    return target.classList.contains("contentP") ? 20 : 49;
  },
  duration: 1,
  stagger: 0.2,
  scrollTrigger: {
    trigger: ".trigger-container",
    start: "20% 100%",
    markers: true,
    toggleActions: "play none none none"
  }
});

I don't know why you'd need to put each one into a separate useEffect(), though. And if you're only animating one thing (contentP), a "stagger" is kinda pointless :)

 

Happy tweening!

Link to comment
Share on other sites

No ideally I want one useEffect, but in that sample I want to give the "contentP" different animation values.
For example "HeadlineFirst" and "HeadlineSecond" starts from y:49, but I want "contentP" to start from y:20 as an example. Or what if I wanted to "contentP" to start from opacity:0, but not for  "HeadlineFirst" and "HeadlineSecond".

 

Writing two useEffects won't work because they start at the same time, not giving the stagger effect.

Link to comment
Share on other sites

I think you might be misunderstanding me. 

 

In one useEffect(), you can create any number of animations, and they can be staggered (or not).

 

useEffect(() => {
  gsap.from([headlineFirst, headlineSecond], {
    y: 49,
    duration: 1,
    stagger: 0.2,
    scrollTrigger: {
      trigger: ".trigger-container",
      start: "20% 100%",
      markers: true,
      toggleActions: "play none none none"
    }
  });
  
  gsap.from(contentP, {
      y: 20,
      duration: 1,
      delay: 0.4,
      opacity:0,
      scrollTrigger: {
        trigger: ".trigger-container",
        start: "20% 100%",
        markers: true,
        toggleActions: "play none none none"
      }
    });
}, []);

Or just use a timeline: 

useEffect(() => {
  const tl = gsap.timeline();
  tl.from([headlineFirst, headlineSecond], {
    y: 49,
    duration: 1,
    stagger: 0.2,
    scrollTrigger: {
      trigger: ".trigger-container",
      start: "20% 100%",
      markers: true,
      toggleActions: "play none none none"
    }
  });
  
  tl.from(contentP, {
      y: 20,
      duration: 1,
      opacity:0,
      scrollTrigger: {
        trigger: ".trigger-container",
        start: "20% 100%",
        markers: true,
        toggleActions: "play none none none"
      }
    });
}, []);

 

And I already showed you how you could use a single tween for various values by using a function-based value. Did you notice the function that I used for the "y" value in the previous post? 

y: (i, target) => {
  return target.classList.contains("contentP") ? 20 : 49;
}

 

But again, it's probably simpler to just create a separate tween if you're going to target different values rather than getting fancy with function-based values. It's totally up to you. 

  • Like 1
Link to comment
Share on other sites

Thank you Jack for your thorough explanation and taking the time to respond and providing solutions. I just need to learn more about function-based values for writing it in a single tween. I do understand the function part e.g. "  ()=>{ } " and the ternary operator at the end but will do some self-educating on the "target.classList.contains" part.

Anyway what i was trying to point out, is if you write two separate tweens, and "fake" the stagger effect by giving one of the tweens a "delay", scrollTrigger ignores the delay. Here is your example with one of the tweens having a delay of 1000. Link=> https://codesandbox.io/s/zen-dew-fr0xv?file=/src/App.js

For example if run your code above, but changed the delay to be more noticeable, does the delay or opacity work on your side?

 

useEffect(() => {
  gsap.from([headlineFirst, headlineSecond], {
    y: 49,
    duration: 1,
    stagger: 0.2,
    scrollTrigger: {
      trigger: ".trigger-container",
      start: "20% 100%",
      markers: true,
      toggleActions: "play none none none"
    }
  });
  
  gsap.from(contentP, {
      y: 20,
      duration: 1,
      delay: 1000,
      opacity:0,
      scrollTrigger: {
        trigger: ".trigger-container",
        start: "20% 100%",
        markers: true,
        toggleActions: "play none none none"
      }
    });
}, []);

 

Link to comment
Share on other sites

  • Solution

Regardless I managed to create the desired effect, thanks to you Jack

 

useEffect(() => {
    gsap.from([headlineFirst, headlineSecond , contentP], {
        y: (i, target) => {
            return target.classList.contains("contentP") ? 20 : 49;
          },
        opacity: (i, target) => {
            return target.classList.contains("contentP") ? 0 : 1;
          },
        duration: 1,
        ease: Power3.easeOut,
        stagger: 0.3,
        scrollTrigger: {
            trigger: ".aboutV2",
            start: "10% 100%",
            markers: true,
            toggleActions: "play none none pause"
        }
    });
    }, []);

 

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