Jump to content
Search Community

Scrolltrigger horizontal - updating width for dynamic elements

blurthelines limited test
Moderator Tag

Go to solution Solved by GreenSock,

Recommended Posts

Hi - I know there must be an easy fix for this but I read the forums/eg etc - and can't get it working - any help would be appreciated.

 

I created a very basic codepen example to illustrate basic problem.

 

- I have a dynamic amount of horizontal cards that extend beyond the width of the screen. (there always need to be in 1 row ie no vertical scrolling)

- each card can open or close to a new width based on a button (their is also an option to open/close all the cards at once) and the overall width of the container expands to fit.

Simple that all works fine.

 

However when I add scroll trigger (I want to users vertical mouse scroll to move the user horizontally thro the timeline) - it no longer updates the width of the overall container correctly.

I have tried using the 'end' to use a function to determine the width, invalidateOnRefresh etc.

 

any clues?

 

 

See the Pen JjBdOVz?editors=1111 by b-t-l (@b-t-l) on CodePen

Link to comment
Share on other sites

Hey there! Thanks for the super clear demo.

So the key here is that you need to tell scrollTrigger to update after the elements have transitioned to their new size

 

I've added an onComplete to your tweens that resize the sections, and then called ScrollTrigger.refresh() in there. Sorted!

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

  • Like 1
Link to comment
Share on other sites

thanks for such a quick reply... really appreciated.

 

I checked your code - but is it just me or is it still not working?

 

The idea is that no matter how many cards are opened the width of the container is staying wide enough to see all the cards when you scroll right.

When I don't use the scroll trigger my little updateTimelineWidth() function does the job just fine.

But add the scroll trigger and it doesn't work anymore.

 

- if you scroll to the end on the cards (their are 20) you see some green space (which is the container on which the scrolltrigger is acting)

- then if you open a few cards (do the end ones so you can see the green space to the right e.g card 17 + 18 + 19) you'll see the the trigger isn't updating the width as the green space gets smaller until you lose a few cards.

 

 

Link to comment
Share on other sites

3 hours ago, blurthelines limited said:

- if you scroll to the end on the cards (their are 20) you see some green space (which is the container on which the scrolltrigger is acting)

 

Isn't that because your code is explicitly setting it incorrectly here: $("#timeline").width(timeline_width)? If I remove ScrollTrigger, it makes no difference - the animation runs right away and I see a chunk of green on the side. So I think you're just miscalculating that timeline_width value. It's adding 600px of "timeline_buffer_width". Don't you just want to remove that from the width calculation? Maybe I'm misunderstanding.

  • Like 3
Link to comment
Share on other sites

Hi - I only added the green to see the edge :)

I have removed it on the codepen if makes the issue clearer.

Go to the end of the timeline (scroll all the way right) - then click open on the last card and you'll see that it disappears offscreen. this is because the overall width of the timeline (container) isn't being updated and so the item is falling below the row.

 

Here's a fork that doesn't have the scroll trigger so you can see how it's suppose to work (you'll have to scroll sideways to move along the timeline):

See the Pen jOpPgKp by b-t-l (@b-t-l) on CodePen

 

And here the scrolltrigger added:

 

See the Pen JjBdOVz by b-t-l (@b-t-l) on CodePen

 

 

I did some offline testing and think I'm getting closer to the issue:

- If I remove the scroll trigger code and just run the update width function the width of the container does expand and all the items stay in 1 row.

- when adding the scroll trigger back in and looking at the Dom elements I can see that the update width function runs and gives a new value to the container but the pinspacer dom element added by scroll trigger doenst update its width on scroll trigger.refresh() - and I can't update it via javascript - it's width stays the same as when the scroll trigger was first initialised no matter what you do.

 

Link to comment
Share on other sites

Ah, okay, besides the fact that your CodePen was using really outdated versions of the GSAP files, the main issue was caused by the fact that ScrollTrigger must record the ORIGINAL inline styles on the pinned element when you create the ScrollTrigger so that it can properly revert it during the refresh(). Remember, in order for ScrollTrigger to do all its magic, it must add various inline styles. So in your case, you set the initial "width" inline and then created the ScrollTrigger. So let's say it was set to 6000. Great, ScrollTrigger records that. Then, later when you manually change that to 6300, for example, your changes don't get applied internally to where ScrollTrigger recorded the original state. It's considered "dirty" after the initial creation. So when ScrollTrigger does its refresh and temporarily reverts things, they get reverted to a width of 6000 because that's what you had originally...and then it does its measurements accordingly. 

 

[side note: it's EXTREMELY unusual for people to dynamically change the dimensions of a pinned element like this. In fact, I've never seen anyone do it...ever :)

 

So now that we know the cause, the solution is relatively simple - you can basically kill the old ScrollTrigger, set the new width on your element, and then re-create the ScrollTrigger so that it records that new value as the "initial" state to revert to on refreshes: 

 

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

 

Is that what you were looking for? 

Link to comment
Share on other sites

Hi - yes - that method does sort the problem out! thanks 

 

Does does however create a small new problem I'd need to resolve, and that is that when scroll trigger is re initialised it will jump back to the start of the scroll. so if you clicked open a card in the middle then you'll lose your place.

 

I'm doing some reading as I'm sure there must be a relatively easy way to set the initial position of the scroll trigger when reinitialised and match that to some calculated x value noted before reinitialising.

 

I'll post a final pen here once I get that sorted for others to use.

 

 

  • Like 1
Link to comment
Share on other sites

thanks Jack and Cassie - that's exactly what I was hoping for but couldn't get done on my own. 

I'm so impressed by how quickly and patiently you have helped what I'm sure seems like a small query.

huge huge thanks !

 

to help any others reading this topic - I have cleaned up the code, added comments etc so it's easier to understand and reuse for others:

 

See the Pen abjdKPN?editors=1111 by b-t-l (@b-t-l) on CodePen

 

 

 

 

  • Like 3
Link to comment
Share on other sites

Happy to help!

 

Just to further explain WHY that was happening, when we create the ScrollTrigger that pins, it adds the pinSpacing (a bunch of extra padding to the bottom to push other stuff down below further down on the page) which stretches out the available scrolling area of course. But then when we kill() that ScrollTrigger, all that pinSpacing goes away, so the available scrollable area shrinks back again. In other words, imagine the page only has 50px of scroll available natively (before you add any ScrollTrigger stuff) and then ScrollTrigger adds 5000px worth of pinSpacing. Then you scrolled 2000px down the page and clicked one of those buttons which kills the old ScrollTrigger before recreating a new one - well, immediately after killing that ScrollTrigger, pinSpacing is gone, so the page shrinks back to only allowing 50px of scrollable area. So if you were previously at 2000px, the browser is like "no way, buddy, I can't do that - the page isn't tall enough to reach that scroll position". 

 

That's why I just record the current scroll position, then do the killing/recreating, and finally restore the scroll position.

 

I hope that clears things up. 

  • Like 1
Link to comment
Share on other sites

thanks - that does help clear up what's happening and lets me understand what's happening under the hood.

 

 

- Sorry to take this a step further but I am adding some anchor tags so it can scroll you to the correct card via the url.

- you'll see I added a button on the left that I am simply using to illustrate my problem here.

- when someone clicks the button the scroller should scroll to that card - number 10 (the card should have it's start point on the left of the screen).

- it should be relatively simple calculate the y position where the card is on the scroller, then tell the scroller to go to that pixel value.

 

the problem is the scroller doesn't go anywhere near that point.

any clues?

 

p.s I added some extra code to the button to show I am calculating the position of the target card via 2 methods (.position() + looping thro). both calculate the same value and the scroller is certainly at that point (I added a listener to console out the current pos) but the card isn't in the correct place.

just can't figure it out

 

See the Pen abjdKPN?editors=1111 by b-t-l (@b-t-l) on CodePen

 

Link to comment
Share on other sites

You're conflating the horizontal and vertical distances. Let's say card 10 is exactly 1000px from the left side and the TOTAL horizontal distance that the entire container moves (at the maximum) is 2000px, thus it's exactly halfway. But your ScrollTrigger's total vertical scrolling distance is 4000px. That means there's twice as much vertical scrolling distance compared to the horizontal movement (thus you scroll 2 pixels to move 1 pixel horizontally). See the issue? You're trying to map the horizontal distance 1:1 to vertical scrolling distance. So in this example, you'd need to scroll to halfway through the entire available scroll distance in that ScrollTrigger. 

 

Here's a fork where I calculate a multiplier onRefresh() that gets the ratio for you and then you can just multiply your horizontal distance by that to get the corresponding vertical scroll position: 

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

 

Better? 

  • Like 1
Link to comment
Share on other sites

  • 1 year later...

I want to do this same thing but in React without using jQuery I tried but I am not able to do this .  whenever I click on the button to expand the box it refreshes and moves to the beginning (the first item). any solution

currently I am using {xPercent : -100} it is working but it scrolls a extra....

and also in your code you have this on line 40 in timelineScroll variable 

x: () => innerWidth

from where this   innerWidth  is coming from ????

 

 

Link to comment
Share on other sites

Without a minimal demo, it's very difficult to troubleshoot; the issue could be caused by CSS, markup, a third party library, a 3rd party script, etc. Would you please provide a very simple CodePen or Stackblitz that illustrates the issue? 

 

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

 

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

that loads all the plugins. Just click "fork" at the bottom right and make your minimal demo

 

Using a framework/library like React, Vue, Next, etc.? 

CodePen isn't always ideal for these tools, so here are some Stackblitz starter templates that you can fork and import the gsap-trial NPM package for using any of the bonus plugins: 

 

Please share the StackBlitz link directly to the file in question (where you've put the GSAP code) so we don't need to hunt through all the files. 

 

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

Since you're using React

Proper cleanup is very important with frameworks, but especially with React. React 18 runs in strict mode locally by default which causes your useEffect() and useLayoutEffect() to get called TWICE.

 

Since GSAP 3.12, we have the useGSAP() hook (the NPM package is here) that simplifies creating and cleaning up animations in React (including Next, Remix, etc). It's a drop-in replacement for useEffect()/useLayoutEffect(). All the GSAP-related objects (animations, ScrollTriggers, etc.) created while the function executes get collected and then reverted when the hook gets torn down.

 

Here is how it works:

const container = useRef(); // the root level element of your component (for scoping selector text which is optional)

useGSAP(() => {
  // gsap code here...
}, { dependencies: [endX], scope: container }); // config object offers maximum flexibility

Or if you prefer, you can use the same method signature as useEffect():

useGSAP(() => {
  // gsap code here...
}, [endX]); // simple dependency Array setup like useEffect()

This pattern follows React's best practices.

 

We strongly recommend reading the React guide we've put together at https://gsap.com/resources/React/

 

If you still need help, here's a React starter template that you can fork to create a minimal demo illustrating whatever issue you're running into. Post a link to your fork back here and we'd be happy to take a peek and answer any GSAP-related questions you have. Just use simple colored <div> elements in your demo; no need to recreate your whole project with client artwork, etc. The simpler the better. 

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