Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...
Théophile Avoyne

ScrollTrigger + React: trouble when pinnings multiple elements on the same page

Go to solution Solved by GreenSock,

Recommended Posts

Hello everyone,

 

This is a reformulation of a topic that I created a few days ago. I am creating a new one here because I think it is more a ScrollTrigger+React-related problem and that it might benefit from being referenced as such.

 

Context

There are two consecutive "sections" that are both a 100vw/100vh. Each section is a React component (in the CodeSandbox below, they're called respectively WorkOverview and HomeAbout). They both get pinned one after the other.

 

Problem

The second element gets pinned too early, exactly as if the padding of the first section's .pin-spacer wasn't taken into account. The weirdest thing is that it doesn't happen all the time (but must of the time). Please note that (1) the ScrollTriggers are created in the order they happen on the page and that (2) it is not caused by any asynchronously-loaded content on what the sections' sizing might rely (images are inside a pre-sized container). Here's a video that illustrate the problem:

 

 

Here's the CodeSandbox link https://codesandbox.io/s/clever-rhodes-16ic1. Note: if you don't see the problem, refresh the page 1 or 2 times.

 

Thanks in advance for you precious help!

Link to comment
Share on other sites

I'd check to make sure your kill function for ScrollTrigger is working as intended.

If you turn markers on, you can usually see if they are being added/duplicated and not removed, otherwise, try to console.log the ScrollTrigger instance after kill it in your garbage cleanup.

If this is the case, you may need to give your trigger an ID then kill it like:

ScrollTrigger.getById("myID").kill();
  • Like 2
Link to comment
Share on other sites

If you resize the page it fixes itself. So it has something to do with creation order.

  • Like 2
Link to comment
Share on other sites

3 hours ago, elegantseagulls said:

I'd check to make sure your kill function for ScrollTrigger is working as intended.

 

Thanks @elegantseagulls. I'm not sure why you think the problem has anything to do with this. The ScrollTrigger never has to be killed (and never is) since there's only one page.

 

3 hours ago, ZachSaucier said:

If you resize the page it fixes itself. So it has something to do with creation order.

 

Thanks @ZachSaucier. Believe me it hasn't. I added a console.log() after each ScrollTrigger in the CodeSandbox, so that you can see by yourself. But I agree, it's the only thing that could have explained what's happening (except for the part that sometimes it works like a charm). There's a race condition, but it seems not to be coming from my code!

Link to comment
Share on other sites

I recommend continuing to strip out stuff until you get to an absolute minimum base case. That will hopefully clearly show where the issue is stemming from.

  • Like 1
Link to comment
Share on other sites

Hmm. It seems to work fine for me in that demo.

 

What do you think is going wrong? What OS and browser are you using? 

Link to comment
Share on other sites

Maybe you clicked on a tab, which caused the window to resize and ScrollTrigger to refresh. Try opening the pen in a new window, and refresh the page if you don't see the glitch. Tested on Chrome, Safari and Firefox.

 

 

  • Thanks 1
Link to comment
Share on other sites

  • Solution

@Théophile Avoyne Thanks so much for putting together that minimal demo. Super helpful. I wish all users were as considerate.

 

Short answer:

Either call refresh() on the timeline's ScrollTrigger instance after you're done adding things to it (and before creating ScrollTriggers for things further down on the page) or just call ScrollTrigger.refresh() after you create all your ScrollTriggers.

See the Pen 8ff7ed837db8924f3745cb5b2600774e?editors=0010 by GreenSock (@GreenSock) on CodePen

 

Explanation:

One of the more complex pieces of building a tool like ScrollTrigger is factoring in the cascading effects of pinning and all the animations. Your demo exposes a very tricky scenario that isn't so much a bug as a logical impossibility, but it's easily worked around by the solution I offered above. 

 

Timelines are a unique beast because when you create one with a ScrollTrigger like this...

gsap.timeline({ scrollTrigger: {...}});

...no animations have been added  yet (and those might affect the start/end positions), so ScrollTrigger waits for one tick before calling its refresh(). That's the method that causes it to calculate its start and end positions. There's no way for it to automatically know when you've added the last animation to the timeline, hence the need for the one-tick delay. However, if the timeline's ScrollTrigger performs pinning, it could affect all the ScrollTriggers further down on the page. That's the case in your demo.

 

For example, if it pins an element for 500px (assuming pinSpacing isn't false), all the subsequent ScrollTriggers would need their start/end values increased by 500px. If you then create ScrollTriggers that aren't in timelines, their start/end values will be calculated right away (before the previous timeline's scrollTrigger calculates its start/end...because of the 1-tick delay), so they won't factor in the pinning offset. Why? Because when they try to look up the chain to factor in how long pinning occurs in previous ScrollTriggers, that value isn't calculated yet for that timeline-based one.

 

This can easily be solved by either manually calling refresh() on the timeline after you're done adding all the animations to it (and BEFORE you create subsequent ScrollTriggers further down the page) or simply call the static ScrollTrigger.refresh() method after you're done creating everything. 

 

Note: you can get the ScrollTrigger instance associated with a particular animation via its scrollTrigger property:

// create a timeline with a ScrollTrigger
const tl = gsap.timeline({ scrollTrigger: {...}});

// after adding all animations to that timeline, we can manually force it to calculate its start/end (on just that instance):
tl.scrollTrigger.refresh();

// or cause ALL ScrollTrigger instances to refresh using the static method:
ScrollTrigger.refresh();

So basically, in your demo you created a timeline-based ScrollTrigger for the red #section-1 which waits for 1 tick to figure out its start/end positions but you then immediately created another ScrollTrigger for the blue #section-2 and since it's not timeline-based, it immediately calculated its start/end and those didn't factor in the timeline's ScrollTrigger pinning.  And interestingly, you were running your code in a setTimeout() so when ScrollTrigger did its initial refresh() call when the page finishes loading, your stuff wasn't created yet. 

 

Does that clear things up? 

 

After further consideration, I decided to add some code to the next release of ScrollTrigger to sense this condition better and if a subsequent ScrollTrigger finds an un-refreshed ScrollTrigger before it, it'll force a refresh() call on that instance at that moment. There's still a risk that the user hasn't finished adding animations to that timeline which could affect things, but I think that's an even more rare scenario. You can preview that at https://assets.codepen.io/16327/ScrollTrigger.min.js

 

And here's a fork of your demo with that preview in place (you may need to clear your cache): 

See the Pen 373c2f96012b24007507967c38f2d4dd?editors=0010 by GreenSock (@GreenSock) on CodePen

  • Like 4
Link to comment
Share on other sites

Hi Jack,

 

Thanks a lot for your in-depth explanation. It's crystal clear. The two alternatives you provided indeed fix the issue (both in the context of the demo and the website I'm working on).

 

7 hours ago, GreenSock said:

I decided to add some code to the next release of ScrollTrigger to sense this condition better and if a subsequent ScrollTrigger finds an un-refreshed ScrollTrigger before it, it'll force a refresh() call on that instance at that moment.

This sounds like a great fix.

 

By the way, thanks a lot for everything you guys are doing. GreenSock products are GREAT, I really enjoy using them everyday 👏

 

 

  • Like 3
Link to comment
Share on other sites

  • 4 months later...

Hi @Théophile Avoyne.
Could you please provide your final piece of code in react that is related to animation?
I have just one ScrollTrigger instance that get initialized at the wrong time similar to what's happening on your first video and calling refresh doesn't help.
The only thing which helps is setTimeout

P.S resizing the window fixes the problem. Start marker moves to the right position on the page

Link to comment
Share on other sites

Do you think you can make a minimal demo? You really shouldn't rely on setTimeout as that is not accurate.

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