Jump to content

Pollux Septimus

Does Pin from ScrollTrigger interferes with CSS styling?

Go to solution Solved by akapowl,

Recommended Posts


I am having an issue where if I add pin: true to scrollTrigger it adds padding to the top and bottom, witch I know is does, but does not pin anything and it looks like I simply added a large padding to the top and bottom of the section. 


I was wondering if this is because of the CSS. Does ScrollTrigger change some stylings that I already changed in the CSS file?


I've created a minimal code sanbox :https://codesandbox.io/s/stoic-mccarthy-h4qykw?file=/src/pages/Homepage/sections/About/index.jsx

Link to comment
Share on other sites

ScrollTrigger uses the native scroll to do its thing, it looks like you're hacking the scroll with some other library and thus ScrollTrigger is not seeing any scroll movement and thus is doing nothing. 


GSAP has it's own smooth scroll library aptly named SmoothScroll which works great with ScrollTrigger and the rest of the GSAP plugins. 



  • Like 2
Link to comment
Share on other sites

Hello @mvaneijgen,

How did you know? 

In my project I do use locomotive-scroll but even when I do not use it the scrollTrigger does not work. I specifically did not include anything related to loco scroll because I did not tough that loco scroll was the problem. as everything else works fine except the pin. 


Can you please tell me how did you know that I am suing another library from the code sandbox? That would help me debug it the issue. 

Link to comment
Share on other sites

The dead giveaway was this attribute: data-scroll-container




One problem I saw right away was: 

trigger: pinContainerRef.current,
start: "top bottom",
scrub: 1,
pin: pinContainerRef.current

So you're telling it to pin the element when the top of the element hits the BOTTOM of the viewport, meaning it is not even in the viewport yet. It'll be pinned down below, so you can't even see it while it's pinned. 


You also have scroller: ".App" which means you're telling ScrollTrigger to only watch for scroll events on that ".App" element...but in your demo, that thing isn't the scroller. It's the main document/body that has the scrollbar. 


By the way, you don't need to wrap stuff in a css: {} object. That's from like 2012 :)


Here's a quick fork: 


  • Like 2
Link to comment
Share on other sites


You pointed out some good mistakes I was making. This gives me some ideas to try to make it work. I hope I could fix it. 


When it comes to the data-scroll-container I tired to eliminate everything that has to do with the loco-scroll trying to debug it. It seems like I forgot about that one I thought it should make a difference. 


Thank you very much! Hope I can debug it, it's been a while since I am trying to figure it out.

Link to comment
Share on other sites

That attribute won't make any difference whatsoever with GSAP/ScrollTrigger. It's totally fine. 


If you still have problems, feel free to fork my demo and reproduce the problem and post it back here with your question. Good luck!


And yes, you might want to consider ScrollSmoother which is nicely integrated with ScrollTrigger. 👍

Link to comment
Share on other sites

Thank you!


Stopping the locomotive scroll and deleting scroller: '.App' does make the pin from from scrollTrigger work. The issue is that the pin does not work with the locomotive-scroll. It might be a ether from the locomotive-scroll or I did not set up scrollerProxy properly. 


I have tried all the ideas I got from your reply but unfortunately it's still not working.

Link to comment
Share on other sites



Do you have an updated example with the changes you mention?


I looked at the first example you posted and I still see the issues Jack pointed out plus the fact that you have several useEffect hooks with empty dependencies array. In the hero file you have two useEffect hooks that are creating a ScrollTrigger instance for the same element and with the same configuration. Why not just one ScrollTrigger instance for that element?


It would be great if you could share a minimal demo that has everything in a single useEffect/useLayoutEffect hook with comments of what you're trying to do in order to know what is not working.


Happy Tweening!

Link to comment
Share on other sites

  • Solution


Hello there.


I am no React expert, so I won't be able to help much with that side of things, but here are some things I noticed.


1) From what I can tell you from a quick glance, there appears to be something wrong with how you're setting up locomotive-scroll, since you are creating multiple instances of it (possibly because of the double-call shenanigans of React ?) - I suppose that will create conflicts with ScrollTrigger - not saying that this is the only or true source of the issues you are facing, but to me that definitely something you might want to fix.






2) I'm not sure how much this following does affect things, but the fact that your ScrollTriggers are being created, before your locomotive-scroll instance was created, doesn't look right to me either - it should be the other way around; first the locomotive-instance should be created (with the relevant ScrollerProxy etc.), then the ScrollTriggers.






3) One thing that I can tell for (pretty much) sure though, is that ScrollTrigger, but at the very least ST's pinning, will not work properly with locomotive's data-scroll-section attribute, which you appear to be using. Locomotive-scroll will apply transforms to that section for the smooth-scrolling effect when you use it, which will interfere with what ScrollTrigger does to that section. In a smooth-scrolling scenario as such (using transforms), the pinType will have to be used as 'transform' to create the pinning-effect; but those transforms applied by ST will probably contradict with the transforms locomotive-scroll applies to that section. See the problem?


When that attribute is removed from your Section component things at least appear to be working better.




Yet, things stop working again as soon as I disable StrictMode for React.

Which I think mioght be because of the order of creation not being correct, as mentioned in 2)


I hope this will help a bit with getting things sorted.




One more thing:


I just now realized, you are using locomotive-scrol v4 - they made some changes to how loco-scroll works, which also affect how the ScrollerProxy needs to be set up. That's also something you might need to consider.





  • Like 1
Link to comment
Share on other sites

Hello @akapowl,

You are totally right. 

1) I've noticed this, it's very bad and it's a problem with itself as is even worse then it should be. Loco-scroll would create 2 scroll bars and the use is able to grad both of them. If that happens when a bunch of problems appear. But simply scrolling dose not seem to effect the scrollTrigger in any meaningful way. 


2) How did you log the creation of scrollTrigger? This also doesn't seem do effect the scrollTrigger in any way. I am imagine because the native scroll no loner exists and the scrollTrigger has no other option but to follow the scrollerProxy. But I would look into it. It seems like something that would cause problems along the way.                                                                                                                                                                                                                                    

3) This is kind a weird. I know about this and gsap talk about this in their post about loco-scroll and scrollTrigger. This is way I added this 

   pinType: document.querySelector(".App").style.transform ? "transform"  "fixed"

to not run into this issue. But apparently this line of code created the issue. All I did was to remove this from the useLocomotiveScroll hook and everything works fine so far... I would not guess in a million years that this was coursing the problem.                                                                                                                                                                                                                                                                                                                                                                                           Thank you very much your your help! I appreciate it!

Link to comment
Share on other sites


1 hour ago, Pollux Septimus said:

1) ... But simply scrolling dose not seem to effect the scrollTrigger in any meaningful way. 


While this doesn't seem to affect anything, it sure is problematic - there is occasions when you can get both instances to scroll, and if they both report back different data to ScrollTrigger, I assume ST is going to be very confused and you won't see the results you expected at some point.




1 hour ago, Pollux Septimus said:

2) ... How did you log the creation of scrollTrigger?


I simply just added some custom console.log('...')s to the hooks you are using.




1 hour ago, Pollux Septimus said:

3) ... I know about this and gsap talk about this in their post about loco-scroll and scrollTrigger.


That line you mentioned is not what I was talking about though. I was referring to locomotive-scroll's data-scroll-section attribute, which according to their docs can help improve performance for locomotive-scroll - but as I said, it will most likely cause conflicting behaviour with ScrollTrigger.




Here are some examples in vanilla JS to show how the points I mentioned above do create issues.


[Edit: Keep in mind, that those examples need to be opened in a seperate window, because with a viewport as narrow as the preview's iFrame, locomotive-scroll will default back to native browser scrolling]


This is the basis for all the examples; here with native browser scrolling - works fine, all good.


See the Pen yLqpZxK by akapowl (@akapowl) on CodePen




This here is the same example but with locomotive-scroll's default behaviour implemented, not using the data-scroll-section attribute - works fine.


See the Pen bGjazzR by akapowl (@akapowl) on CodePen




And here is that exact same example, only with the data-scroll-section attribute added to the individual sections (similar to what you are doing in your demo) - this is what I meant with my 3)rd bullet point. As you can see, things become problematic with the pinning ScrollTrigger there, on the second section.


See the Pen qBypvWW by akapowl (@akapowl) on CodePen




Last, but definitely not least, here is that basic locomotive-scroll example again, but this time with the order of creation messed up. In this example I create the ScrollTriggers first, before I even initialize locomotive-scroll and set up the scrollerProxy etc. - again, not working too well.


See the Pen XWBVGrG by akapowl (@akapowl) on CodePen




So all in all, I think that's where (at least some of) your problems stem from, although I'm not saying that this is definitely all, as I didin't have the time to check every single bit in your code. The biggest sources of your problems seem to be a) React - its double-calling in Strict Mode and its order of creating your page in general; and b) locomotive-scroll and its data-scroll-section attribute that isn't really compatible with ScrollTrigger.


  • Like 2
Link to comment
Share on other sites

So, @Pollux Septimus, I've had some more tinkering with this, and I think I've come to a working outcome ( but since I am not too familiar with React in particular, I give no guarantee that this is the holy grail when it comes to this).


Point 3) of what I mentioned still stands - do not use locomotive-scroll's data-scroll-section attribute for the reasons mentioned.


Point 1) looks like it boils down to the double-rendering in React's Strict mode from v18 upwards - so just like you do with GSAP's .context(), you will need to do some sort of clean-up to prevent multiple instances of loco-scroll to be created.


So now I added 


return () => { 
  ScrollTrigger.removeEventListener('refresh', lsUpdate);


to the end of the useEffect of your custom useLocoScroll hook.


Point 2) can apparently not really be avoided, but React offers you some way to workaround the problem that this boild down to.


The way I understand things, you probably shouldn't be using a useEffect but rather a useLayoutEffect for your custom hook:


  • useLayoutEffect: If you need to mutate the DOM and/or do need to perform measurements
  • useEffect: If you don't need to interact with the DOM at all or your DOM changes are unobservable (seriously, most of the time you should use this).


taken from here:






Those things changed, I landed on this, which appears to be working just like intended. Does that work better for you?




  • Like 2
Link to comment
Share on other sites



I also tried to make an example that is a bit more condensed, to make it at least a little bit easier to see through what's going on there.


@Rodrigo would you mind having a look at this if you find the time, to see if this is somewhat acceptable from the React side of things?






Worth mentioning on the side:


Since elements and even whole sections kept disappearing on me when scrolling, I added a suggested sort of workaround to that known locomotive-scroll issue to the end of the styles.css


  • Like 3
Link to comment
Share on other sites

Hi @akapowl

Sorry for the not responding sooner, been bussie with work. 


The reason it work in you demo is because the locomotive-scroll is not working at all. You can see that by the scroll bar. When locomotive-scroll is working the scroll bat is the yellow scroll bar and has the locomotive-scroll styling. 


I have made a simpler example where is more evident if the locomotive-scroll is working or not: https://codesandbox.io/s/misty-moon-sch8xv?file=/src/hooks/useLocoScroll.js.


Removing the data-scroll-section seems to help, also removing the 

      pinType: document.querySelector(".App").style.transform
        ? "transform"
        : "fixed"

from the hook also enable the pin on the scrollTrigger. 


Tring to fix the issue with the double scroll bar from locomotive-scroll I have tried a similar think as you did. This is what I've tired:

    return () => {
      if (locoScroll) {
        ScrollTrigger.removeEventListener('refresh', lsUpdate);
        locoScroll = null;

Unfortunately this just makes the pin not to work at all. 


Your idea of using useLayoutEffect inside the hook it's actually what makes this work, almost. This allows the prev code to work, from the useLayoutEffect return function fixing the double scrollbar issue, the one that you suggested at point 1. Interesting enough using useLayoutEffect anywhere else in the code completely brakes the scrollTrigger again in a similar way. I don't know why this is happening, maybe someone from Gsap that is more familiar with how gsap works with useEffect and useLayoutEffect could give us an explanation. 


The reason I said it almost works is because in my "real" project I have a glitch that unfraternally is not visible in the codesanbox. The issue is when ever the pin should start a small jigger or lag appears. Adding a pin on the hero section on the logo with the start set to "top top" shows that the scroll is happening for a few pixels and then gets pined when it should have been pinned from the start. It's like taking a few pxs to realize that it should have been pinned. 

I think this is because the scrollTriggers still gets initialized before the locomotive-scroll.


I have make some research on how I could delay the scrollTrigger from being created. One solution would be to use the setTimeout. But I am not a big fan on using this fix because I would have to use it evetime I create an animation that uses the pin. 

I have come across the fac that you could kill the scroll trigger so I am assuming that you could initialized it again but I don't know how I could kill all the scrollTriggers at the same time or have them killed by default and initialized them only if the loco-scroll is true. I know that scrollTrigger has a default prop that allows you to set properties as default for all the scrollTriggers so I will try to use that. 


I will make some more research on how I could do that and if I don't find an answer I'll probably open another thread and see if someone from gsap can help me.


Thank you very much for taking the time! I truly appreciate it! 

Your input helped me a lot. 



Link to comment
Share on other sites


2 hours ago, Pollux Septimus said:

The reason it work in you demo is because the locomotive-scroll is not working at all. You can see that by the scroll bar. When locomotive-scroll is working the scroll bat is the yellow scroll bar and has the locomotive-scroll styling. 




Works just fine for me. Maybe you need to resize your viewing-window and reload, to make sure locomotive-scroll actually uses the transform-smooth-scrolling ? ...because as already mentioned, it will revert back to native browser scrolling when loaded below a certain window width.


Which is also why you should not remove this line


pinType: document.querySelector(".App").style.transform
        ? "transform"
        : "fixed"


because that line makes sure that when locomotive-scroll reverts back to native browser scrolling, ST will use position fixed for the pinning. As your scroller is not the body, otherwise it will be using 'transform' by default, which will make your pins appear rather jittery when locomotive-scroll reverts back to native browser scrolling.




My demo actually still does have some issues - with regard to things not working quite right on resize - but for the time being I don't have time to tinker with this some more.

  • Like 1
Link to comment
Share on other sites

Interesting, It did not worked for me even when full screen.


The issues from resizing can be fixed by adding using the useIsMobile hook. Adding it to the useLeyoutEffect watchers array should fix those issues.

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.