Jump to content
GreenSock

Jure

Opacity resetting on second timeline with ScrollTrigger

Recommended Posts

I'm building a full height header where I first animate some text in and then fade it out using ScrollTrigger.

 

gsap.registerPlugin(ScrollTrigger);

// let intro = gsap.timeline();
// intro
//   .from(".t1", {lazy: false, y: "-3vh", opacity: 0, duration: 0.6})
//   .from(".t2", {lazy: false, y: "-2.5vh", opacity: 0, duration: 0.6}, 0.3)
//   .from(".t3", {lazy: false, y: "-2vh", opacity: 0, duration: 0.6}, 0.6);

let outro = gsap.timeline({
  scrollTrigger: {
    trigger: ".hero",
    start: "top top",
    scrub: 0.3,
    pin: true,
    markers: true,
  }
});
outro
  .to(".t1", {lazy: false, y:"-50vh", scale: 0.96, opacity: 0, duration: 0.3})
  .to(".t2", {lazy: false, y:"-49vh", scale: 0.95, opacity: 0, duration: 0.3}, 0)
  .to(".t3", {lazy: false, y:"-48vh", scale: 0.94, opacity: 0, duration: 0.3}, 0);

 

If I disable the first timeline (intro), the second one (outro) works fine.
If I have both timelines enabled, the second timeline text just jumps to opacity 0 immediately.

I've build a Codepen example showing the problem exactly.

 

Thanks for any tips on what I'm doing wrong.

 

See the Pen 9e13a1b2af05410e39daa11b48854465 by visibledots (@visibledots) on CodePen

Link to comment
Share on other sites

Hi Jure,

 

You have competing animations that are supposed to be starting at the same time. I would just create some wrapper elements and divide the animations up that way.

 

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

 

  • Thanks 1
Link to comment
Share on other sites

Hi Blake,

 

thanks so much - I can solve it by adding more wrapper elements.

 

Just curious - I understand that both timelines compete for animating the same elements, but the first timeline (intro) has had enough time to fully complete before I start animating (by scrolling) the second timeline - so I don't understand why the second timeline would not just pick up where the current DOM state is. Is there some GSAP caching involved?

 

Is there a standard approach for solving this without using wrapper elements? Perhaps to run the first (intro) timeline first, then enable the ScrollTrigger (or second timeline) via a callback? If this is too complex, I can let it go and just use wrapper elements.

Link to comment
Share on other sites

43 minutes ago, Jure said:

Is there a standard approach for solving this without using wrapper elements?

 

You just need to be mindful when animating the same element in different places so they don't conflict.

 

 

It also helps to understand how animations are created. Every animation has a start and end value, and then GSAP just interpolates between those 2 values.

 

So when you do this, GSAP is going to use the element's current opacity as the end value and then set the opacity to 0 and use that as the start value.

 

intro.from(".t1", {lazy: false, y: "-3vh", opacity: 0, duration: 0.6})

console.log("opacity", gsap.getProperty(".t1", "opacity")) // opacity 0

 

Now when you go create your other animation, it's going to use the element's current opacity as the start value, which is 0 because it was set above in your from animation, and also have 0 as the end value. So it's essentially going to an opacity from 0 to 0, and that's why it goes blank.

 

outro.to(".t1", {lazy: false, y:"-50vh", scale: 0.96, opacity: 0, duration: 0.3})

 

Now if both animations used ScrollTrigger scrubbing, you could set immediateRender to false and the second animation and would be okay because GSAP won't record the start values of the second animation until after the first animation has played, kind of like here.

 

 

 

43 minutes ago, Jure said:

Perhaps to run the first (intro) timeline first, then enable the ScrollTrigger (or second timeline) via a callback?

 

I guess you could do this...

 

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

 

And I know you're going to ask how come it's not recording 0 for the opacity as the start value for the outro animation, and that's because the animation hasn't rendered yet because it's paused. All ScrollTrigger animations have immediatRender set to true by default.

 

So technically if you did this to your ScrollTrigger and did not start scrolling until your intro animation finished, your  ScrollTrigger animation would work, but of course you can't make the user sit there and wait for the intro animation to finish. 

 

let outro = gsap.timeline({
  scrollTrigger: {
    trigger: ".hero",
    start: "10 top",
    scrub: 0.3,
    pin: true,
    markers: true,
    immediateRender: false
  }
});

 

Makes sense?

 

 

  • Thanks 1
Link to comment
Share on other sites

Yes, this all makes absolute sense, thanks so much for the in-depth explanation!

 

I know that I can't count on the user waiting there without interrupting the animation, I was just curious how it would work and you've really cleared up how immediateRender works on competing ScrollTriggers.


Just as reference for other people reading this - I remember watching a tutorial on how to block the user from scrolling before the initial animation has run on Creative Coding Club - https://www.creativecodingclub.com/courses/take/scrolltrigger-express/texts/15589254-prevent-scroll-on-fullscreen-intro

 

This approach hides all content below the initial header (so there's nothing to scroll and use ScrollTrigger) and then enabling the content after the first timeline has finished (onComplete). 

 

(not sure if that tutorial is subscription only, but I can vouch the subscription is 100% worth it)

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