Jump to content
Search Community

ScrollTrigger pin inconsistent start - depends on scroll speed

Romann test
Moderator Tag

Recommended Posts

 

 

 

by some reason my element pins fine if i scroll slow, but if i scroll really fast pin start in a very offset position. What could be the issue ? 

For the smooth scrolling i am using simple LERP value i apply with transform:translate on my wrapper  Also "document.documentElement.scrollTop" is available at any point in chrome console, so i don't think i am doing any scroll jacking according to scrolltrigger documentation. I did read about scrollerProxy() but i cant figure out how to connect it with my own scroll smoothing javascript and do i have to if everything else works exempt inconsistent pin start depends on how fast i scroll ?

 

This are some pieces of my smooth scroll (no library used)

 

let docScroll;
const getPageYScroll = () => (docScroll = window.pageYOffset || document.documentElement.scrollTop);
window.addEventListener("scroll", getPageYScroll);

 

      this.renderedStyles = {
        translationY: {
          // interpolated value
          previous: 0,
          // current value
          current: 0,
          // amount to interpolate
          ease: 0.15,

          // current value setter
          // in this case the value of the translation will be the same like the document scroll
          setValue: () => docScroll
        }
      };

 

  // sets the initial value (no interpolation) - translate the scroll value
      for (const key in this.renderedStyles) {
        this.renderedStyles[key].current = this.renderedStyles[
          key
        ].previous = this.renderedStyles[key].setValue();
      }

 

        this.DOM.scrollable.style.transform = `translate3d(0,${-1 *this.renderedStyles.translationY.previous}px,0)`;

 

 

This is my scrolltrigger pin:

 

I have to use pinReparent since my smoothscroll wraps everything in div layer that is transformed Y 

 

      let pin = ScrollTrigger.create({
        id: "Pinned", 
        trigger: ".pin-on",
        start: "50% 50%",
        end:"5000",
        pin: true,
        // scrub:1,
        pinReparent: true,
        markers:true,
        // pinSpacing:false,
        anticipatePin: 1
      })

 

if this is not clear enough, let me know i will assemble simplified version of the issue

 

 

 

Link to comment
Share on other sites

If I understand your question correctly, it's because you've got anticipatePin set to 1. Just remove that. The entire point of anticipatePin is to do what you're seeing - it watches the speed that you're scrolling and tries to anticipate when it should pin, and do it early because most modern browsers handle scrolling on a different thread, so if you scroll quickly and there's a lot of work the browser has to do when pinning, you might see it scroll slightly past where it was supposed to for a fraction of a second before pinning. This has nothing to do with GSAP or bugs in ScrollTrigger - it's just a logic issue with how browsers handle scrolling and repainting. 

 

So remove anticipatePin altogether and see if that resolves things for you :)

Link to comment
Share on other sites

2 hours ago, GreenSock said:

If I understand your question correctly, it's because you've got anticipatePin set to 1. Just remove that. The entire point of anticipatePin is to do what you're seeing - it watches the speed that you're scrolling and tries to anticipate when it should pin, and do it early because most modern browsers handle scrolling on a different thread, so if you scroll quickly and there's a lot of work the browser has to do when pinning, you might see it scroll slightly past where it was supposed to for a fraction of a second before pinning. This has nothing to do with GSAP or bugs in ScrollTrigger - it's just a logic issue with how browsers handle scrolling and repainting. 

 

So remove anticipatePin altogether and see if that resolves things for you :)

 

 

Thanks Jack,

 

Unfortunately this did not help, here is live demo on codepen:

 

See the Pen PobBaOL by ivashnev (@ivashnev) on CodePen

 

 

 

Link to comment
Share on other sites

Ah, I didn't have time to read through your whole post originally and now I see that it's definitely caused by your scroll-jacking smooth-scrolling stuff. I didn't realize you were scroll-jacking, sorry. 

 

Basically, the page's scroll position is updated immediately (and that's what ScrollTrigger uses for its calculations), but the content on the page itself is being slowed down by your scroll-jacking, thus the pinning occurs before your content "catches up". 

 

Unfortunately, we just don't have the resources to troubleshoot custom scroll-jacking code that you've written but I'm confident that you could get it working with ScrollTrigger by using the scrollerProxy() method. Let us know if you have any GSAP-specific questions we can answer for you. 

  • Like 2
Link to comment
Share on other sites

14 hours ago, GreenSock said:

Ah, I didn't have time to read through your whole post originally and now I see that it's definitely caused by your scroll-jacking smooth-scrolling stuff. I didn't realize you were scroll-jacking, sorry. 

 

Basically, the page's scroll position is updated immediately (and that's what ScrollTrigger uses for its calculations), but the content on the page itself is being slowed down by your scroll-jacking, thus the pinning occurs before your content "catches up". 

 

Unfortunately, we just don't have the resources to troubleshoot custom scroll-jacking code that you've written but I'm confident that you could get it working with ScrollTrigger by using the scrollerProxy() method. Let us know if you have any GSAP-specific questions we can answer for you. 

 

Thank you for the advice,

 

I am trying to implement scrollerProxy functionality but unfortunately i dont see any effect, this is what i did:

 

  scroll = new Scroll();

// scroller proxy setup
// scrollable element "scroll.DOM.scrollable",
// interpolated position value scrollTop -  "scroll.renderedStyles.translationY.previous"

ScrollTrigger.scrollerProxy(scroll.DOM.scrollable, {
  
      scrollTop(value) {
        if (arguments.length) {
            scroll.renderedStyles.translationY.previous = value; // setter
        }
        return  scroll.renderedStyles.translationY.previous;    // getter
      }

  })

I did check examples provided on the scrollerProxy() page but all 3 examples are working with a libraries and there is no very basic example available. 

 

My question is: What parameters from my simple smooth scroll i need to provide to scrollerProxy in order to connect this two ? 

My understanding is the only parameter needed  - is interpolated value of scrollTop.

 

 

 

Link to comment
Share on other sites

Did you set your ScrollTrigger's "scroller" to the thing you're using for the proxy (scroll.DOM.scrollable)? Remember, the whole point of scrollerProxy() is to tell ScrollTrigger to use your proxy in the place of a particular DOM element when it's trying to determine its scroll position. So if, for example, your ScrollTrigger is using the default scroller (the main viewport), but you set up a scrollerProxy() for some other element, it won't have any effect. 

  • Like 2
Link to comment
Share on other sites

2 hours ago, GreenSock said:

Did you set your ScrollTrigger's "scroller" to the thing you're using for the proxy (scroll.DOM.scrollable)? Remember, the whole point of scrollerProxy() is to tell ScrollTrigger to use your proxy in the place of a particular DOM element when it's trying to determine its scroll position. So if, for example, your ScrollTrigger is using the default scroller (the main viewport), but you set up a scrollerProxy() for some other element, it won't have any effect. 

 

Jack, thank you this helped i can see it is working now and pinning mechanics fully synced with interpolated value, one problem i have now i lost ability to control start and end trigger, specifically by some reason scroller-start and scroller end markers are now part of the scrollable element so they scroll with the page instead of positioned to the screen size. what could be the problem ? 

 

See the Pen PobBaOL by ivashnev (@ivashnev) on CodePen

 

 

 

Link to comment
Share on other sites

3 hours ago, Romann said:

thank you this helped i can see it is working now and pinning mechanics fully synced with interpolated value

Excellent!

 

3 hours ago, Romann said:

i lost ability to control start and end trigger, specifically by some reason scroller-start and scroller end markers are now part of the scrollable element so they scroll with the page instead of positioned to the screen size. what could be the problem ?

That's because you're moving the entire <body> which is where the markers are. Typically when people do smooth scrolling, they do so with a wrapper element, NOT the <body> itself. Have you tried putting all your content into a <div> that you apply the smooth scrolling to instead of the <body>? 

  • Like 1
Link to comment
Share on other sites

Thank you Jack,

 

actually in my example i am not moving body tag, i have exactly what you described typical smooth scroll approach and still markers are not positioned absolutely to the screen but moving together with the content.

 

In addition i. did another example and i used exclusively GSAP and scrolltrigger to make smoothscrolling effect, so no 3rd party plugins used and i still have the same issue: scroll start markers scrolling with content and overall not able to control when pinning exactly starts.

 

I would greatly appreciate is someone can take a look and tell me why i don't have good control of when pin starts/stops

 

here is 100% gsap/scrolltrigger example with not properly working pin:

 

See the Pen YzpJzxB by ivashnev (@ivashnev) on CodePen

 

 

Link to comment
Share on other sites

I noticed quite a few things:

  1. You had an invalid start value of "top " - it should be a space-delimited value with two values, like "top top" - see the docs for details.
  2. It seemed like a very odd setup because you're setting the trigger to the body itself...and you're also using the default scroller which is the viewport (typically the body). So when you set it up with top: "top top", end: "bottom bottom", you're basically saying "start when the top of the body reaches the top of the body" and "end when the bottom of the body reaches the bottom of the body" - sorta perplexing. See what I mean? Maybe you meant end: "bottom top"
  3. Then later, after you've already created some ScrollTriggers, you set the default scroller to be the container (ScrollTrigger.defaults({ scroller: container });). Did you intend to run that first? 
  4. You're using a scrollerProxy() that attempts to tap into a valuetopass object ({}), but you forgot to set an initial y value, thus the getter would return undefined.
  5. I have no idea what you're trying to do with that final ScrollTrigger that has pinReparent: true

I think it'd be much better if you could just slowly build something up from the most basic stuff rather than trying to do so many thing that seem to be causing misunderstandings. For example, maybe just try to get one thing to pin or trigger at the spot you want. THEN, once that works, move onto something that does the pinReparent (which is almost never needed) and then finally the smooth scrolling. I just feel like there are so many things that are jumbled in your demo that they're obfuscating each other and muddying the waters. I don't mean that in an insulting way at all - I'm simply trying to offer some advice to get to a solution in the fastest, most efficient way possible. 

  • Like 2
Link to comment
Share on other sites

19 hours ago, GreenSock said:

I noticed quite a few things:

  1. You had an invalid start value of "top " - it should be a space-delimited value with two values, like "top top" - see the docs for details.
  2. It seemed like a very odd setup because you're setting the trigger to the body itself...and you're also using the default scroller which is the viewport (typically the body). So when you set it up with top: "top top", end: "bottom bottom", you're basically saying "start when the top of the body reaches the top of the body" and "end when the bottom of the body reaches the bottom of the body" - sorta perplexing. See what I mean? Maybe you meant end: "bottom top"
  3. Then later, after you've already created some ScrollTriggers, you set the default scroller to be the container (ScrollTrigger.defaults({ scroller: container });). Did you intend to run that first? 
  4. You're using a scrollerProxy() that attempts to tap into a valuetopass object ({}), but you forgot to set an initial y value, thus the getter would return undefined.
  5. I have no idea what you're trying to do with that final ScrollTrigger that has pinReparent: true

I think it'd be much better if you could just slowly build something up from the most basic stuff rather than trying to do so many thing that seem to be causing misunderstandings. For example, maybe just try to get one thing to pin or trigger at the spot you want. THEN, once that works, move onto something that does the pinReparent (which is almost never needed) and then finally the smooth scrolling. I just feel like there are so many things that are jumbled in your demo that they're obfuscating each other and muddying the waters. I don't mean that in an insulting way at all - I'm simply trying to offer some advice to get to a solution in the fastest, most efficient way possible. 

 

Thank you for the advice, i really appreciate it, I believe i fixed issues you pointed out, I originally used my own simple smooth scroll but i thought it is more confusing for you to look at the code because it is something not GSAP related so that's why i switched to fully native scroll-trigger smooth scroll and now i have two scroll triggers and i am confused even more.

 

1. Did i correctly setup scrollrpoxy and scrolltrigger smooth scrolling ?

2. How can i make my second scroll trigger pin work ? 

 

 

let container = document.querySelector("#scroll-container");

// ScrollTrigger.defaults({ scroller: container });

ScrollTrigger.scrollerProxy(container, {
  
    scrollTop(value) {
      if (arguments.length) {
        valuetopass.y = value; // setter
      }
      return  valuetopass.y;    // getter
    },
    getBoundingClientRect() {
      return {top: 0, left: 0, width: window.innerWidth, height: window.innerHeight};
  }

})
let valuetopass = {y:0}

let arr = []
let height = container.scrollHeight;
document.body.style.height = height + "px";

arr.push(container,valuetopass)

arr.forEach(box =>{ 

gsap.to(box, {
    y: -(height - window.innerHeight),
    scrollTrigger: {
      // scroller:container,
      trigger: document.body, // if i use anything else here it does not work
      start: "top top",
      end: "bottom top",
      scrub: 1,
      // markers:true,
    }
  });

})

  ScrollTrigger.create({
    scroller: container,
    trigger: ".pin-on",
    start: "top center",
    end:"bottom bottom",
    pin: true,
    // pinType: "fixed",
    scrub:1,
    // pinReparent: true,
    markers:true,
    // pinSpacing:false,
    // anticipatePin: 1
  })

 

Updated codepen:

 

See the Pen YzpJzxB by ivashnev (@ivashnev) on CodePen

 

Link to comment
Share on other sites

There are so many confusing things about your setup, I'm not really sure what to say. There are multiple scrollbars, you're reporting negative scrollTop values (those should pretty much always be positive), you're animating a generic object and an element in the same way in your forEach(), you're also trying to involve separate pinning logic...I don't quite know where to start, what exactly you're trying to do, or how to really help you here. 

 

And keep in mind that we specifically steered clear of smooth scrolling in ScrollTrigger because there are a lot of quirks you'll run into related to that and it wasn't something we wanted to support. So I'm reluctant to have this thread turn into "help me build smooth scrolling into ScrollTrigger from scratch". 

 

I think maybe it'd be best to take a step back and make sure you understand what a scrollerProxy is. Normally, ScrollTrigger watches a particular scroller element and gets/sets its scrollTop, for example. So when the user scrolls it by 100px, a scroll event is fired (well, probably many) at which point ScrollTrigger says "hey scroller, what's your current scrollTop because I've gotta check to see if I need to activate any ScrollTriggers accordingly". A scrollerProxy() is something that sits inbetween and lets you control that communication so that when ScrollTrigger gets the scrollTop, for example, YOUR code can decide what to feed to ScrollTrigger. Same for setting it. 

 

So in your demo, you're moving an element up (negative y), along with the valuetopass.y and inverting what should get reported/saved in the scrollerProxy(). In other words, scrolling down on a web page makes it look like the content is going up (negative y) but it reports the scrollTop as a higher and higher value (not negative). 

 

I'm totally taking some stabs in the dark, but maybe this fork helps move you in the right direction a bit?: 

See the Pen 12fc944b00672ee5fd6f76bd0b89677b?editors=0010 by GreenSock (@GreenSock) on CodePen

 

Good luck!

  • Like 1
Link to comment
Share on other sites

24 minutes ago, GreenSock said:

There are so many confusing things about your setup, I'm not really sure what to say. There are multiple scrollbars, you're reporting negative scrollTop values (those should pretty much always be positive), you're animating a generic object and an element in the same way in your forEach(), you're also trying to involve separate pinning logic...I don't quite know where to start, what exactly you're trying to do, or how to really help you here. 

 

Jack, thank you for your time working on such a thoughtful  reply, I really appreciate it, i don't want to waste your time anymore,  but i have to ask last question, why you think animating a generic object is not an award winning idea ? The reason I am animating generic object "valuetopass" so i can pass the same Y value to the scrollproxy getter and setter.  You think cleaner way to achieve the same effect exist, without animatic a generic object ? 

Link to comment
Share on other sites

4 minutes ago, Romann said:

why you think animating a generic object is not an award winning idea ? The reason I am animating generic object "valuetopass" so i can pass the same Y value to the scrollproxy getter and setter.  You think cleaner way to achieve the same effect exist, without animatic a generic object ? 

Oh, I didn't mean to imply it was "bad" - it just struck me as a bit odd to have it written the way you had it in your code, where you were looping through completely different types of objects (element and generic object) and creating an identical tween for each. It's fine, though, and that's one of the things that makes GSAP special - it can animate literally any property of any object (including generic objects). 

 

If it were my code, I may lean toward using one "source of truth" by using the y value of the container since you're already animating that anyway. You can tap into gsap.getProperty(). But really, it's totally fine to have two tweens animating different objects to the same property values if that seems to make more sense to you. 

 

Enjoy!

  • Like 2
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...