Jump to content
Search Community

Pairing Vertical and Horizontal scroll pinning with ScrollTrigger

ekfuhrmann test
Moderator Tag

Recommended Posts

I've just started dipping my toes into scrollTrigger, but am running into a few issues.

 

The examples offered by GSAP are fantastic, but I'm struggling to understand how to bring a few of them together into a cohesive page. What I'm looking to do is have a page that has vertical pinning sections, which then has a single section (Section 3) that contains a horizontal list of cards which scroll through horizontally, before then continuing on vertically.

 

I've tried to calculate the "height" of the horizontal scrolling section to then give Section 3 the right amount of "vertical scrolling" to account for the horizontal scrolling, but then my vertical section pinning is all thrown out of whack. In the end, I think I'm just going about this wrong so hoping one of you could impart some knowledge on me.

 

Thanks!

See the Pen NWpGeZX by ekfuhrmann (@ekfuhrmann) on CodePen

Link to comment
Share on other sites

 

Hey @ekfuhrmann.

 

Here is how I would tackle that logically:

 

Loop through all your sections and inside that forEach-loop check if the section in scope has a certain class ( 'horizontal' here - I added that in the HTML markup).

 

If the section has that class, create a ScrollTrigger with a fake-horizontal-scroll-tween on the wrapper around the cards attached to it (which you currently don't have in your demo), if it doesn't, just create that other pinning ScrollTrigger you have for normal sections. This way you can also make sure (at least in this scenario) that your ScrollTriggers are being created in order of appearance on the page. If for whatever reason you couldn't create them in order of appearance on the page in your code, you could call ScrollTrigger.sort() at the end of creation or set a refreshPriority to whatever ScrollTrigger neccessary.

 

I set up the horizontal-fake-scroll-tween here so it translates that wrapper to the left so your last card will end up centered flush with the card below, which might be antother tricky step along the way. Also tricky would be to incorporate snapping appropriatly in this scenario so I left that out here because that's just a bit too much for me to grasp myself at the moment.

 

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

 

 

 

On a sidenote:

pinSpacing is set to true by default on pinning ScrollTriggers, so there is no need to actually set pinSpacing: true - only if you don't want pinSpacing, you'd need to clarify that.

 

That might be a bit much to grasp for the beginning (of ScrollTriggering), so I hope that is somewhat understandable overall.

 

Cheers,

Paul

 

  • Like 3
  • Thanks 1
Link to comment
Share on other sites

Thanks @Cassie.

 

Just as a sidenote to this:

The calculation for the x-translation of the horizontal wrapper could be simplified a bit (since there is a lot of window.innerHeight in there).

 

x: () => { return -( (cardsWrap.scrollWidth - window.innerWidth + window.innerWidth*0.05) + (window.innerWidth/2 - oneCard.offsetWidth/2) ) }

 

I left it like that on purpose to sort of have a reconstruction of the thinking process underlying that calculation.

 

Like;

You could remove everything but the cardsWrap.scrollWidth from it and see how it behaves with only that - moving the container way too far to the left - that's why you'd have to substract the (first) window.innerWidth from it to make it end on the right edge of the screen. ...but not quite yet because there is still the padding on the section that you'd have to respect (+ window.innerWidth*0.05 ). Now you'd have it end flush to the right edge of the window in the end.

 

So for centering up, you'd now want to have it 'moved' to the left by half the window size again first - which would make the right edge of the card sit flush against the center of the window - and then move it to the right again for half the width of the card itself - and now it's centered.

 

This is just my thinking process when it comes to concepts like that.

I like to do it step by step because grasping the bigger picture at once can be tricky.

 

You can always go ahead and simplify whereever possible once you've got it running properly in the first place.

 

  • Like 4
Link to comment
Share on other sites

@akapowl This has been incredibly helpful, thank you! I'm still trying to wrap my head around the idea that the ScrollTriggers are all working off each instance of an element, in this case .section, so you just use a conditional to handle what happens with that specific section based on the scroll trigger event.

 

To your point of simplifying the x calculation, I found that return -cardsWrap.scrollWidth + oneCard.offsetWidth; works quite well and you can check out the updated codepen below!

 

I have 2 additional minor questions for you if you've got the time.

  1. Is there any way to modify the "scroll velocity" for the horizontal section?
  2. I was using a fixed hero on my site so that the content would scroll over top of it (basically pinning the hero), but would it not be better to use ScrollTrigger to do that since it's already handling the rest of the page?

 

codepen

  • Like 1
Link to comment
Share on other sites

54 minutes ago, ekfuhrmann said:

To your point of simplifying the x calculation, I found that return -cardsWrap.scrollWidth + oneCard.offsetWidth; works quite well and you can check out the updated codepen below!

 

😅 

Well yeah, I guess that is what's effectively left over in the end - I could have already seen that myself after that first step I mentioned above - but I tend to overcomplicate sometimes. Good job!

 

 

 

1 hour ago, ekfuhrmann said:

Is there any way to modify the "scroll velocity" for the horizontal section?

 

end: () => "+=" + (cardsWrap.scrollWidth - oneCard.offsetWidth)

 

should feel more natural already. Using the scroll-wheel of my mouse, for me it moves the sections to the left for the same amount of pixels that it does move them upwards on vertical scroll with that. Maybe see if that is already better for you.

 

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

 

The end of the ScrollTrigger is what you want to adjust when it comes to the speed of the horizontal scrubbing tween. The less pixels there are for the scrubbing tween to fit into, the faster it will appear - something like end: "+=100px" e.g. would make it super fast. Is that what you mean by velocity?

 

 

 

1 hour ago, ekfuhrmann said:

I was using a fixed hero on my site so that the content would scroll over top of it (basically pinning the hero), but would it not be better to use ScrollTrigger to do that since it's already handling the rest of the page?

 

You certainly could do that, but if you've already got it working the way you want, it probably wouldn't make much sense to change some basic native functionality to do the same with JS.

 

There have been some threads recently along those lines that wanted to incorporate an animation on the 'fixed' header that I'll just link - in case that is something you'd want to consider.

 

 

 

 

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

2 hours ago, akapowl said:

The end of the ScrollTrigger is what you want to adjust when it comes to the speed of the horizontal scrubbing tween. The less pixels there are for the scrubbing tween to fit into, the faster it will appear - something like end: "+=100px" e.g. would make it super fast. Is that what you mean by velocity?

 

Yes this is exactly what I was looking for, thank you for clarifying that for me.

 

2 hours ago, akapowl said:

There have been some threads recently along those lines that wanted to incorporate an animation on the 'fixed' header that I'll just link - in case that is something you'd want to consider.

 

I fortunately do not think I need to worry about this for my current project, but I really do appreciate the other resources to use as reference should it come up.

 

Thank you again for being so helpful with this, I really do appreciate the help immensely.

  • Like 3
Link to comment
Share on other sites

  • 1 year later...

 

10 minutes ago, hastalavistababy said:

Not working on mobile

 

Hello! Unfortunately that is not a very good description of what the problem is you are encountering. My best guess is that you just need to set overflow-x to hidden not only on the body but also to the fake-horizontal .section as also described in the thread linked below.

 

If that does not solve what is not working for you, please describe your issue in a bit more detail and I'll see if I can help. Thanks!

 

 

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

  • Like 2
Link to comment
Share on other sites

2 hours ago, akapowl said:

 

 

Hello! Unfortunately that is not a very good description of what the problem is you are encountering. My best guess is that you just need to set overflow-x to hidden not only on the body but also to the fake-horizontal .section as also described in the thread linked below.

 

If that does not solve what is not working for you, please describe your issue in a bit more detail and I'll see if I can help. Thanks!

 

 

 

 

Now its working on mobile safari, but gliching when scrolling.

 

Check: Example

Link to comment
Share on other sites

  • 1 month later...

I'm trying to do almost the same thing but with snapping also but without any luck so far.

 

I have no issues snapping in the horizontal ScrollTrigger but no luck for the other vertical ones.

Here's what I got using this template: 

See the Pen gOeZKOJ?editors=0010 by nicvh (@nicvh) on CodePen

 

On my actual template I'm able to snap vertically but not between vertical and horizontal ScrollTriggers.

Link to comment
Share on other sites

In that setup, it may be simplest to just use CSS snapping. It's possible with ScrollTrigger snapping, but you'd need to handle it in a custom way using a function-based value that calculates the closest vertical one and skips the horizontal. Not trivial. It's beyond the scope of help we typically provide here, but you could post in the Jobs & Freelance forum if you need more help with that. 

Link to comment
Share on other sites

I think CSS snapping is not an option for me as I'm using gsap scrolltrigger to slide up and pin sections as in this example: 

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

 

Looks like I've got to figure out the custom way.

To be honest that's what I tried to look at these last days, I was able to use `window.scrollTo()` when scrolling down to go to the next section when leaving a horizontal scrolltrigger but unfortunately no luck going up so far. Also, most importantly the CSS scrolling effect even when using smooth looks different from gsap's (of course but not fixable as far as I can tell).

 

You said "calculates the closest vertical one and skips the horizontal" but either I am not following or that's not what I'm actually after.

Indeed, on my setup snapping within vertical sliders and horizontal sliders work just fine, the issue is when I go from on slider to another especially leaving a horizontal scrolltrigger to a vertical one. It doesn't snap at all to the vertical one, but weirdly the other way around works (when scrolling up from horizontal to vertical).

Even if I do the right calculations and get the correct scrolltrigger instance, how do I trigger the snap programmatically?

Link to comment
Share on other sites

Hey - it would help if you had your actual demo as an example rather than two different pens - makes it harder to advise.

But yeah the key is going to be a function for snap. 

snap Number | Array | Function | Object | "labels" | "labelsDirectional" - Allows you to snap to certain progress values (between 0 and 1) after the user stops scrolling. So snap: 0.1 would snap in increments of 0.1 (10%, 20%, 30%, etc.). snap: [0, 0.1, 0.5, 0.8, 1] would only let it come to rest on one of those specific progress values. It can be any of the following:
  • Number - snap: 0.1 snaps in increments of 0.1 (10%, 20%, 30%, etc.). If you have a certain number of sections, simply do 1 / (sections - 1).
  • Array - snap: [0, 0.1, 0.5, 0.8, 1] snaps to the closest progress value in the Array in the direction of the last scroll (unless you set directional: false).
  • Function - snap: (value) => Math.round(value / 0.2) * 0.2 feeds the natural destination value (based on velocity) into the function and uses whatever is returned as the final progress value (in this case increments of 0.2), so you can run whatever logic you want. These values should always be between 0 and 1 indicating the progress of the animation, so 0.5 would be in the middle.

 

Link to comment
Share on other sites

Thank you @Cassie

I didn't put my scenario to a codepen because I'm not looking for a solution but more clues as I know js pretty well, but gsap not so much.

 

I've had a go at the snap function but unfortunately that won't work in my case as the `snap` callback function is not getting fired when scrolling after the last horizontal slide.

 

Looks to me my only way is listening to window `scroll` event and work out the current slide from it, `gsap.to()` to the next vertical/horizontal scrolltrigger. Unless there's a better way?

Link to comment
Share on other sites

We can't really give you the right clues unless we see the problem you're trying to solve really, it's so dependant on situation.

 

6 minutes ago, nicvh said:

I've had a go at the snap function but unfortunately that won't work in my case as the `snap` callback function is not getting fired when scrolling after the last horizontal slide.

No idea what this means! Snap in this case isn't a callback, it's a function that returns instructions for the snapping.

You can grab progress value using onUpdate? Maybe that'll help, this route's pretty dependant on the sections being evenly spaced though. It won't work for every layout, especially if the sections take up different ratios on different screen sizes.
 

 

 

Link to comment
Share on other sites

  • 2 weeks later...

So I've kind of implemented this correctly but I'm having a few issues so hoping someone can help me out.

 

Issue 1

I changed my sections to be auto width instead of 100% width like the example in this thread:

 

See the Pen gOeZKOJ by nicvh (@nicvh) on CodePen

 

This all seems to work great until I get to the last section inside a horizontal scroller. It seems to add a huge gap at the end of the horizontal scroller. There is no padding between the sections as I thought it might be calculating it and adding it. 

 

Video of issue:

 When I add a vertical scrolling section, the functionality seems to work but that huge gap is still there:

 

 Here is the javascript code that I've got going:

 

gsap.utils.toArray('[data-component="scrolling-wrapper"]').forEach((section) => {
      if (section.dataset.type === "horizontal") {
        const cards = section.querySelector(".scrolling__panels");
        const card = section.querySelector(".scrolling__panel");

        gsap.to(cards, {
          x: () => {
            return -cards.scrollWidth + card.offsetWidth;
          },
          ease: "none",
          scrollTrigger: {
            trigger: section,
            start: () => "center center",
            end: () => `+=${cards.scrollWidth - card.offsetWidth}`,
            scrub: true,
            pin: true,
            invalidateOnRefresh: true,
            anticipatePin: 1
          }
        })
      } else {
        ScrollTrigger.create({
          markers: false,
          trigger: section,
          start: () => "top top",
          pin: false,
          anticipatePin: 1
        })
      }
    })

 

This is being called on window.load

 

Issue 2

I've tried to implement this into GSAP ScrollSmoother and have had some success but not 100%. When ScrollSmoother is on, it doesn't seem to calculate the vertical scrolling panels and gets cut off.

 

Any help on this would be greatly appreciated. The support in the Greensock Forums is amazing so I'm hoping that someone can help.

 

Thanks

 

 

 

Link to comment
Share on other sites

It's pretty tough to troubleshoot without a minimal demo - the issue could be caused by CSS, markup, a third party library, your browser, an external script that's totally unrelated to GSAP, etc. Would you please provide a very simple CodePen or CodeSandbox that demonstrates the issue? 

 

Please don't include your whole project. Just some colored <div> elements and the GSAP code is best (avoid frameworks if possible). See if you can recreate the issue with as few dependancies as possible. If not, incrementally add code bit by bit until it breaks. Usually people solve their own issues during this process! If not, then at least we have a reduced test case which greatly increases your chances of getting a relevant answer.

 

Here's a starter CodePen that loads all the plugins. Just click "fork" at the bottom right and make your minimal demo

See the Pen aYYOdN by GreenSock (@GreenSock) on CodePen

 

If you're using something like React/Next/Nuxt/Gatsby or some other framework, you may find CodeSandbox easier to use. 

 

Once we see an isolated demo, we'll do our best to jump in and help with your GSAP-specific questions. 

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