Jump to content
Search Community

How to reset ScrollTrigger trigger position?

DouggieO test
Moderator Tag

Recommended Posts

Hi there! We just completed a major site redesign featuring ScrollTrigger in several modules. It's been awesome, thanks to all involved in making this happen!

 

One issue that came up is illustrated in the Codepen. We have a few modules that "expand" with interaction, like the green Module 2. The horizontal scroll module works great, but if the module above is expanded, it throws off the start/end positions of the trigger.

 

My initial solution was to add a resizeObserver on the height of the body, that would trigger a window.resize event. This was effective in conjunction with MatchMedia, but it's global and is interrupting other animations that are happening on-screen while the body size is changing.

 

What's the best (and more "componentized" ) way to recalculate the trigger when its position changes like that?

 

Note: I don't think it's necessarily relevant, but we are using React in a sort of unique set up... we are working within a modular CMS, so each module is its own React app, similar to how the Codepen is set up.

Much thanks!

See the Pen VwPBvmP by DouggieO (@DouggieO) on CodePen

Link to comment
Share on other sites

It does! It’s great to know that .refresh() is the command for the job. 
 

However, in the spirit of modularity, I was hoping the solution could be something implemented in the horizontal scroll component itself, since that’s the component that needs to get refreshed (esp in my case since there isn’t a great way to communicate directly between modules). Is there a good way for the Horizontal scroll module to listen for a change in its trigger element and .refresh() itself?  


A thought that crossed my mind is some sort of event listener (or react hook) that listens for changes to getBoundingClientRect() values of the trigger.  Is there more of a “Greensock way” to do something like that?

Link to comment
Share on other sites

Wouldn't you just add some code to any modules that alter height? They don't need to know what's going on in other modules - ScrollTrigger.refresh() is a global thing anyway. 

 

I'd be careful about listening for changes to the client rect - that sounds like a recipe for performance issues. 

Link to comment
Share on other sites

Thanks for the insight! The only problem is we have a fairly large site with many components, several developers, and an infinite combination of modules on any given page. A developer down the road could introduce some kind of expanding component and inadvertently break the ScrollTrigger modules on any page that ends up on. Therefore we would need to establish a "special rule" for components that might change the position of other components.  If this the only way, that's fine, but the more special rules we have the harder our site is to maintain. So my goal here is to minimize special rules—it would be more ideal if our ScrollTrigger modules could resolve themselves, no matter what else is on the page—expanding components, tabs, drawers, etc. 

 

If listening for changes to the client rect is non-performant, do you have any other ideas how to refresh the triggers within the ScrollTrigger module itself?

Link to comment
Share on other sites

12 hours ago, DouggieO said:

If listening for changes to the client rect is non-performant, do you have any other ideas how to refresh the triggers within the ScrollTrigger module itself?

I don't quite follow - "refresh the triggers within the ScrollTrigger module itself"? You mean other than calling ScrollTrigger.refresh()? 

 

If I understand you correctly, this sounds like an engineering issue - if you're building a page that contains logic that's sensitive to height/layout changes, wouldn't you want to build your modules in a way that'd dispatch events accordingly to notify up the chain? In other words, it'd be a requirement for descendant modules. I totally get that in an ideal world, all the modules would be totally independent and not have any requirements whatsoever for their ancestors, but as far as I can tell, you've got a tough decision to make: either you prioritize "modularity" (no event dispatching or callbacks or whatever), -OR- you prioritize performance. 

 

If you have to listen for every time there's a height change, that's gonna be pretty terrible performance-wise because let's say you've got a 1-second CSS transition that's growing the height of a child element. That means you'd have to wire up listeners on the DOM to sense any size change like that (expensive even on their own) and then on top of that cost, you'd get those firing on every tick during the height transition, thus ScrollTrigger.refresh() would get called WAAAAY too often. And ScrollTrigger.refresh() is quite expensive because in order to work properly, it has to essentially roll back any DOM changes it made (like for pinning) and return the DOM to its native state so it can do accurate measurements and let the normal CSS classes affect things properly. Then you might also run into infinite loops because your height change listeners would trigger ScrollTrigger.refresh() which ITSELF may alter the height of things which would trigger the listeners, and around and around we go. 

 

If I were in your shoes, I'd definitely prioritize performance for your users and take the hit on the engineering side. If something is gonna change the height of the page, it should finish its change and THEN dispatch an event or callback or something. So in our example above, it'd wait until that 1-second transition finishes. That way ScrollTrigger.refresh() gets called once instead of like 60 times. 

 

See what I mean? 

  • Like 1
Link to comment
Share on other sites

Hello!

From what I understand, you want your app2 to finish opening or closing, another component (app3) makes a scrolltrigger.refresh() to recalculate its start and end positions.

 

To inform app3 that app2 has finished animating, you can do so in a number of ways.

 

For example, you could add a global context that has a state inside that is:

 

const [changedHeight, setChangedHeight] = useState(false);

 

This state determines a value throughout your website to inform you if the height of the website has changed.

In app2, you access that global state and once you click to open that component, once it finishes opening, you could launch something like this:

 

setChangedHeight(!changedHeight);

 

At that moment, that value will change informing that the height has been updated on your website. It does not matter if the value is true or false, what matters is that it has changed.

In app3 you should have a useEffect in your code and add the dependency on whether your changedHeight changed.

Something like that:

 

useEffect (() => {
Scrolltrigger.refresh();
}, [changedHeight]);

 

This useEffect will only be fired when the value of changedHeight changes.

Another point, you have to correctly inform when your animation finishes in app2, maybe it would be a better approach with gsap, since when finished you can use a .call() to do:

 

setChangedHeight (!changedHeight);

 

Unfortunately I'm on mobile and I can't write code correctly, but I think this explanation can give you some ideas.

 

All the best!

  • Like 2
Link to comment
Share on other sites

8 hours ago, DouggieO said:

@AnimatiCSS When you say "global context" do you mean the React Context API? I didn't think it was possible to use that across multiple apps/roots?  How else could we do that?

Hi!

Yes, I mean exactly that, React Context API.

Actually the focus of your code should be 1 app that contains components, and not a mix of different Apps.

Is for some reason that focus in your code?

Look here I have organized your code by components within a general APP.

Now it is better focused to add the global context.

If you give me more time, then I will add it in another codepen.

 

See the Pen PoWVwKm by animaticss (@animaticss) on CodePen

 

All the best!

Edited by AnimatiCSS
error in text
Link to comment
Share on other sites

Unfortunately we are a bit handcuffed there... we are on a multipage modular CMS which lets our marketing team build the pages with our modules. We tried to find a way to architect everything into 1 app, and we may still get there, but for now there were too many edge cases where modules could not be done in react so each module has its own root/app so they can be inserted into the dom interchangeably. Make sense?

 

If you have any ideas on how to use the context api across multiple apps, please let me know. I think redux could work for this, but I was hoping to not have to introduce that for the minimal things we’d need it for. I’m also wondering if I can just create a global object in plain js that I can use as a simple global state, and write a hook that listens for changes to it. 

  • Like 1
Link to comment
Share on other sites

3 hours ago, DouggieO said:

Unfortunately we are a bit handcuffed there... we are on a multipage modular CMS which lets our marketing team build the pages with our modules. We tried to find a way to architect everything into 1 app, and we may still get there, but for now there were too many edge cases where modules could not be done in react so each module has its own root/app so they can be inserted into the dom interchangeably. Make sense?

 

If you have any ideas on how to use the context api across multiple apps, please let me know. I think redux could work for this, but I was hoping to not have to introduce that for the minimal things we’d need it for. I’m also wondering if I can just create a global object in plain js that I can use as a simple global state, and write a hook that listens for changes to it. 

In that case, the approach I mentioned will not work.
Maybe in your case you should play with the DOM events.

For example, when APP2 is updated, launch the X function that updates your APP3 (i.e., launch ScrollTrigger.refresh ());)

Then I try to mount a codepen with this solution in case it works for you.

All the best!

Link to comment
Share on other sites

3 hours ago, AnimatiCSS said:

In that case, the approach I mentioned will not work.
Maybe in your case you should play with the DOM events.

For example, when APP2 is updated, launch the X function that updates your APP3 (i.e., launch ScrollTrigger.refresh ());)

Then I try to mount a codepen with this solution in case it works for you.

All the best!

 

I have gotten it to work through events, this solution should be able to implement it.

 

I just modified in the CSS the selector .expandable> div {}

I removed the transition and assigned it by default height: 100px;

 

Then I created a custom event called "recalc" to recalculate the position of the ScrollTrigger when launched.

 

In APP2 I modified the animation (now it is done with GSAP) so that when the animation finishes we can launch what we need (dispatch of the custom event created by us);

 

See the Pen ExZrZmQ by animaticss (@animaticss) on CodePen

 

All the best!

  • Like 2
Link to comment
Share on other sites

  • 2 years later...

This is an old post but thought the following solution could be of interest;  we can use the ResizeObserver. 

// Function to update scrollTrigger positions dynamically
    const updateScrollTrigger = () => {
        elementsRef.current.forEach(element => {
            ScrollTrigger.refresh();
        });
    };

    // Watch for changes in the size of the dynamic height element (#resizeableElement)
    const resizeObserver = new ResizeObserver(updateScrollTrigger);
    resizeObserver.observe(document.getElementById('resizeableElement')); // Use the correct ID

    // Cleanup function
    return () => {
        resizeObserver.disconnect();
    };

 

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