Jump to content
Search Community

Need Help: Broken Scrolltrigger When Adding Second Pin

gregor

Go to solution Solved by gregor,

Recommended Posts

Posted

Hi!

I’ve built an animation where a headline gets pinned in the center while some images scroll by in the background. Then, a block of text scrolls up, hits the bottom of the headline, and at that point, the pin is removed and everything scrolls normally again.

 

Now I want to take it a step further:
After that sequence, I’d like to pin the parent container once it reaches the top, so that the next module can slide over the pinned section. But doing this breaks everything.

 

Here’s a small demo – if you comment out the second part, you’ll see the problem clearly.

 

See the Pen WbQpWor?editors=1011 by destroy90210 (@destroy90210) on CodePen.

Posted

This would still be a sequence eg a timeline, so make everything part of the same timeline and have the last element slide over be part of the whole animation and pin the element that encloses all the elements you want to pin.

 

 

The best thing to do when working with ScrollTrigger is to remove it! This seems counter intuitive, but ScrollTrigger is just animating something on scroll, so just focus on the animation at first and only when you're happy with the animation add ScrollTrigger back in. This way you can focus on one part at a time and it will save a lot of headache when debugging.  
  
Most of the ScrollTrigger effects you see are just moving things on the y-axis instead of scrolling them, then hooking that animation to ScrollTrigger will give the illusion of scrolling, but they are really just animating! Getting to grips with this knowledge will make it so much easier to create all kinds of effects, I've written a whole post about it, check it out:

 

 

Hope it helps and happy tweening! 

 


  
 

 

Posted

Hi,

 

There are a couple of things that should be noticed here.

 

First, pinning an element that resides inside another element that has been pinned already is bound to create a problem. The reason is that pinning uses position fixed in an element that is added by ScrollTrigger (called pinSpacer) around the trigger element. Pinning an element inside a pinned element doesn't behave as expected, but this is not a GSAP/ScrollTrigger issue but simply the way browsers and CSS works. The reason is stacking context, which is explained in this article:

https://www.joshwcomeau.com/css/stacking-contexts/

In this case you're using the second heading line as the trigger but you want to pin the entire parent section, not just the headers section, so that causes the problem here.

 

Second, the start trigger of the second instance passes the start point before the first instance's end trigger passes the end point.

 

I think it's easier to just use the endTrigger feature and pin the headings part longer and pin the roles section independently of the headings section, something like this:

See the Pen LEpyWBX by GreenSock (@GreenSock) on CodePen.

 

Hopefully this helps

Happy Tweening!

Posted

Hi, thanks again for your replies!

 

I’ll follow your advice and pin just one wrapper, then move everything along the Y-axis for the animations.

 

As a first step, I set up the basic animation. I gave the heading full viewport height and positioned the elements below it absolutely, I find that makes calculations easier to manage.

 

Do you think this is a good setup for the animation? Or do you have any tips before I start adding ScrollTrigger back in?

 

See the Pen MYaowYE by destroy90210 (@destroy90210) on CodePen.

 

 

Posted

Personally I like to start with all my elements at a known position stacked on top of each other. Instead of position: absolute I like to use this CSS grid trick then the element still take up height in the document flow. See my post for more details on this 

 

 

This will also make all the calculations a lot easier you just have to reverse the logic of your tweens. So instead of animating them .to() `-=${headingRect.top + headingRect.height / 2}` you can just do .from() yPercent: 100 or y: () => window.innerHeight, again  check my post I explain this all in detail.

 

Hope it helps and happy tweening! 

 

Posted

Yeah, as @mvaneijgen mentions sometimes there is more than one way to achieve the results you're looking for. If the implementation you have in place works for you and is the best for your project, then use it. If you can extract some logic from Mitchel's post or the demo I posted, that is helpful for your use case, again just use it.

 

We don't aim to be holders of absolute truths in these forums, we simply guide and teach as much as we can with the information we have, based on the demos users post. The idea is always give users a nudge in the right direction when it comes to solving GSAP related issues or something else we might see in the code, like basic HTML/CSS/JS stuff.

 

Happy Tweening!

Posted

hi;)

Okay, I’ve read through everything and did my best to add ScrollTrigger back in, but I’m still struggling a bit.

 

I got it working so far: the images slide in behind, and the subheading scrolls into place and stays below the heading. That part looks good.

 

But now I’m trying to slide the heading and subheading up together and it seems like the ScrollTrigger fires too early.


Does that mean GSAP doesn’t wait for that part of the timeline and just runs the ScrollTriggers immediately?

 

Please comment out the last part to see the issue.

.to([heading, roles], {
    y: -window.innerHeight / 2,
    ease: "none",
    scrollTrigger: {
      trigger: heading,
      start: "center center",
      end: `top -${headingRect.height / 2}`,
      scrub: true,
      id: "text",
      markers: true
    }
  });

 

See the Pen MYaowYE?editors=1010 by destroy90210 (@destroy90210) on CodePen.

Posted

The issue here is that you can't nest ScrollTriggers. ScrollTrigger can only be on the whole timeline not on individual tweens

 

See the Pen raOwRRa?editors=0010 by mvaneijgen (@mvaneijgen) on CodePen.

 

I've remove the first block and ScrollTrigger, then installed GSDevTools to easily debug the animation. To me your last (blue) section should be part of the whole section and should just animate .from() off screen as the last tween. I don't use tailwind, so I'm just going to write CSS (this also because CSS is really important when working with GSAP)

 

This is what I think your layout should look like without any Javascript and just HTML and CSS (the three colored sections should also be part of this stack, but I don't know how tailwind works, so I can't fix that)

 

See the Pen azvwMxZ?editors=0010 by mvaneijgen (@mvaneijgen) on CodePen.

 

Than this is what the animation would look like with out any ScrollTrigger

 

See the Pen ZYbyPgY?editors=0010 by mvaneijgen (@mvaneijgen) on CodePen.

 

And then finally call together

 

See the Pen azvwMem?editors=0010 by mvaneijgen (@mvaneijgen) on CodePen.

 

Hope it helps and happy tweening! 

  • Like 2
Posted

Hi, thanks so much,  this really really really helps me.
 

The only problem is that I can't place nextSection inside the wrapper because they are separate components on the page. After some research, I found a solution where positioned fixed is used  for the next section and then returned to its original scrolling position.


However, there is now a problem with fast scrolling. The .nextsection overlaps the next section, so the calculations are wrong when I scroll very fast. It's getting late, so I'll come back to this in the next few days.... but maybe you have an idea for that... but I don't want to abuse you :) 

See the Pen PwPJPrE?editors=1010 by destroy90210 (@destroy90210) on CodePen.

Posted
16 hours ago, gregor said:

However, there is now a problem with fast scrolling. The .nextsection overlaps the next section, so the calculations are wrong when I scroll very fast.

What exactly is the issue in this case? Everything seems to be working as expected, maybe I'm missing something obvious here 🤷‍♂️

 

Have you tried fastScrollEnd?

https://gsap.com/docs/v3/Plugins/ScrollTrigger/#fastScrollEnd

 

Maybe that could help

Happy Tweening!

Posted

It overlaps. window.scrollY or rect.top seems to return the wrong values when scrolling quickly (see screenshot). I set the background opacity to 50% to make it more visible. I will check out fastscrollend to see if it helps.

Bildschirmfoto 2025-08-11 um 21.47.16.png

  • Solution
Posted

Okay, got it, it was much simpler than I expected.
I inspected the DOM and noticed that the .pin-spacer element has exactly the height I need to position the .nextSection element.

 

So, I just updated the offset like this:


.add(() => {
    gsap.set(nextSection, {
      position: "absolute",
      top: () => {
      return (
        triggerRect.top -
        triggerRect.height +
        wrapper.closest(".pin-spacer").offsetHeight
      );,
      width: "100%"
    });
  });

See the Pen PwPJPrE?editors=1010 by destroy90210 (@destroy90210) on CodePen.



thanks for the great support ;)

  • Like 1

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