Jump to content
GreenSock

nickraps

ScrollTrigger wrong start end positions

Recommended Posts

It is quite difficult for me to create a reproduction for this bug because it lives in a complex project. So I am asking for some possible directions/ideas that you can think of. Thanks!

 

The animation is to stagger several elements on Y position - sliding from bottom of page to their original positions.

 

Basically, the start position should be the top of the element's original position:

901614962_ScreenShot2022-02-14at12_33_56.thumb.png.bf0368d87cdfa443c3cc72fa9e61946c.png

 

 

But ScrollTrigger marked the start position way down the page, causing the animation not triggered until scrolled very far:

1026130809_ScreenShot2022-02-14at12_34_05.thumb.png.6f32715994826ae2cbb6ffe237b2ec23.png

 

 

I found this post, in which it was stated it's a bug:

 

 

Here is the code that sets up these (I'm using it in React):

  const headerRef = useRef<HTMLElement>(null)
  const q = gsap.utils.selector(headerRef)
  useEffect(() => {
    const animation = gsap.from(q('.stagger_elements'), {
      y: '100vh',
      stagger: 0.05,
      ease: 'power4.out',
      scrollTrigger: {
        markers: true,
        trigger: headerRef.current,
        toggleActions: 'restart none none none',
      },
    })
    return () => {
      animation.kill()
    }
  }, [])

 

 

And to add more context, it typically works on DEV mode on my local machine. Once deployed, the trigger position is often wrong but sometimes after several refresh, it could get it right occasionally. - that's why I tried to create a demo but cannot reproduce it.

 

Could it be a bug? Any possible work arounds?

I need the start position to be the element's original top (where the red arrow line is).

 

Any ideas would be appreciated.

Link to comment
Share on other sites

Hi nickraps,

 

It's going to be really hard to advise on what the problem is without a minimal demo that clearly shows the issue. When someone says that it's too complicated to reproduce, it is even more even harder to advise because we don't know if something else in your code is messing with it.

 

The only things that I can think of that would cause the positions to be incorrect.

 

1. Something is causing the layout to change after your ScrollTriggers are created, like say the height of an image after it loads, in which case you would need to call ScrollTrigger.refresh().

 

2. You have triggers inside an element that gets pinned, causing the offset to be calculated incorrectly, in which case you would need to use pinnedContainer.

 

Quote
pinnedContainer Element | String - If your ScrollTrigger's trigger/endTrigger element is INSIDE an element that gets pinned by another ScrollTrigger (pretty uncommon), that would cause the start/end positions to be thrown off by however long that pin lasts, so you can set the pinnedContainer to that parent/container element to have ScrollTrigger calculate those offsets accordingly. Again, this is very rarely needed. (added in 3.7.0)

 

3. You are animating your triggers, which is going to put them in a different position. If that's the case, it's better animate a wrapper element instead.

 

// bad
gsap.to(myElement, {
  y: -500,
  scrollTrigger: {
    trigger: myElement
  }
});

// better
gsap.to(myWrapper, {
  y: -500,
  scrollTrigger: {
    trigger: myElement
  }
});

 

Link to comment
Share on other sites

8 minutes ago, OSUblake said:

3. You are animating your triggers, which is going to put them in a different position. If that's the case, it's better animate a wrapper element instead.

Thanks for these tips! @OSUblake

 

I am animating the child elements in the root element, and the root element is the trigger. Would that be a concern?

 

I'll try to create a reproduction demo too.

Link to comment
Share on other sites

@OSUblake

And to add more context, it typically works on DEV mode on my local machine. Once deployed, the trigger position is often wrong but sometimes after several refresh, it could get it right occasionally. - that's why I tried to create a demo but cannot reproduce it.

Link to comment
Share on other sites

13 minutes ago, nickraps said:

I am animating the child elements in the root element, and the root element is the trigger. Would that be a concern?

 

Shouldn't be unless you have some weird pinning going on that React doesn't know about.

 

Link to comment
Share on other sites

@OSUblake Yes, I think it is caused by a header that's pulled on the fly. The header renders an elements then quickly hides it - right when ScrollTrigger is calculating the positions. So the positions got pushed down the height of the header element. It is a mess anyway. I have no control over that header code.

 

So in my case I'll probably just use GSAP for animation, but use something like intersection-observer for triggering it.

Link to comment
Share on other sites

As Blake said earlier, just make sure you fire ScrollTrigger.refresh() when stuff in the DOM is settled so that it can do all its measurements accurately. No need to switch to IntersectionObserver, although that's totally fine if your needs are basic. It just can't do anywhere near what ScrollTrigger can do feature-wise :)

Link to comment
Share on other sites

@GreenSock The difficult thing is the header is pulled from an ASP.NET HTTP web service from another server. I don't have access to any of that code. I can only call the HTTP service.  So, I don't know if I can figure out a way to reliably know when I can refresh in the React UseEffect hook. I tried SetTimeout (which apparently is a bad solution) which delays long enough but causes issues for other things on the page.

 

But thanks, I do agree ScrollTrigger is more powerful.

  • Like 1
Link to comment
Share on other sites

3 minutes ago, nickraps said:

but causes issues for other things on the page.

You're saying that ScrollTrigger.refresh() causes issues on your page? Do you mind me asking how so? 

 

You could set up a setInterval() or something that checks for an effect you can predict with the header (like page height or the containing <div> height or something) and when it senses that change, you fire the ScrollTrigger.refresh() and kill the setInterval(). Just an idea. 

  • Thanks 1
Link to comment
Share on other sites

@GreenSock Oh Thanks for the hint! I just tried refresh() in a setTimeout and it seems to be working. And setInterval should be more reliable, will explore that later.

 

So no, refresh() is good. I was putting the initialization code of the Trigger in setTimeout in a React component, so that it initiates after the header is fully rendered. But that causes other parts on the page that use this component to somehow not accurately calculate the positions or correctly initiate the Trigger.

 

But now I see your hint, I realize I shouldn't initiate the Trigger in a setTimeout.

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