Jump to content
Search Community

ScrollTrigger not working in a grid

seifhadaba test
Moderator Tag

Go to solution Solved by Rodrigo,

Recommended Posts

code sandbox

 

I'm new to GSAP and am struggling getting a certain animation to work using scrollTrigger when my elements are in a grid layout. The animation aims to move the content horizontally as the user scrolls, and I've taken out the relevant components into this code sandbox. Apologies if there's still too much code in there, but I wanted to make sure the code is representative of the components I have in my project, in case there are nuances causing the problem. 

 

In App.tsx, if you uncomment all the code, you'll find a grid layout containing a 'navbar' on the left, and the main content on the right. Currently, with the grid commented out, the animation works fine, but if you include the grid, it all breaks for reasons I don't understand. I've read through the common mistakes for GSAP and scrollTrigger and don't think this is covered. 

 

I also have a couple of other smaller issues as well please in the ExperiencePage.tsx component: 

  • toggleClass on scrollTrigger doesn't apply the .active class to components.
  • the width variable isn't being updated as the browser window is resized, despite me setting invalidateOnRefresh to true and using a function to define the end attribute in the scrollTrigger configuration.

Appreciate any help with this please. 

Link to comment
Share on other sites

  • Solution

Hi @seifhadaba and welcome to the GSAP Forums!

 

In your App.tsx file the main issue is that your layout element has a fixed height of 100vh, so when you uncomment that part there is no scroll available:

.layout {
  display: grid;
  grid-template-columns: 12rem 1fr;
  height: 100vh; /* THIS */
}

Also these styles don't seem to be helping at all:

.content {
  grid-column: 2 / span 1;
  overflow: hidden scroll;
  scroll-snap-type: y proximity;
}

You can just set hidden to both, ScrollTrigger works with the window as the default scroller and unless you have no other choice but to use a different one, is always better to keep it that way, it simplifies everything quite a bit. Also your ScrollTrigger already has a snap function in it, any particular reason to have scroll-snap defined in your CSS? This seems to work:

.layout {
  display: grid;
  grid-template-columns: 12rem 1fr;
}

.content {
  grid-column: 2 / span 1;
  overflow: hidden;
}

In the case of the toggleClass, is doing exactly what you're telling it to do, is adding the ".active" class with the dot in it so your HTML looks like this:

class="_src_components_ExperiencePage_ExperiencePage_module__container .active"

See the dot before active?

// Wrong
toggleClass: ".active",
  
// Right
toggleClass: "active",

Finally if you log in your getWidth you'll see that is ran after you resize the window so that is also working as expected:

const getWidth = () => {
  const margin = Number(
    window
    .getComputedStyle(section1.current!)
                      .getPropertyValue("margin-right")
    .slice(0, -2) // remove "px"
  );
  const width =
        Number(
          window
          .getComputedStyle(section1.current!)
                            .getPropertyValue("width")
          .slice(0, -2)
        ) +
        2 * margin;
  console.log("get width", width);

  return width;
};

You also don't need to add the window width to the dependencies on the useGSAP hook:

useGSAP(() => {}, {
  scope: container,
  dependencies: [window.innerWidth] // <- No need for this
});

ScrollTrigger will run it's refresh method after the window resize and it has a debounce method that optimizes the whole process.

 

Here is a fork of your demo:
https://codesandbox.io/p/sandbox/gsap-scrolltrigger-forked-qjt48r?file=%2Fsrc%2FApp.tsx%3A7%2C5-7%2C29

 

Hopefully this helps.

Happy Tweening!

Link to comment
Share on other sites

Hi @Rodrigo and thanks for the reply! I've fixed some of the things you noted in the sandbox code which hopefully should be updated now if reopen it. 

 

9 hours ago, Rodrigo said:

In your App.tsx file the main issue is that your layout element has a fixed height of 100vh, so when you uncomment that part there is no scroll available:

.layout {
  display: grid;
  grid-template-columns: 12rem 1fr;
  height: 100vh; /* THIS */
}

Thanks for pointing that out. Commenting this out does seem to mostly fix the issue : ) 

 

9 hours ago, Rodrigo said:

Also these styles don't seem to be helping at all:

.content {
  grid-column: 2 / span 1;
  overflow: hidden scroll;
  scroll-snap-type: y proximity;
}

You can just set hidden to both, ScrollTrigger works with the window as the default scroller and unless you have no other choice but to use a different one, is always better to keep it that way, it simplifies everything quite a bit. Also your ScrollTrigger already has a snap function in it, any particular reason to have scroll-snap defined in your CSS? This seems to work:

.layout {
  display: grid;
  grid-template-columns: 12rem 1fr;
}

.content {
  grid-column: 2 / span 1;
  overflow: hidden;
}

You're right, I did intend to just have both set to hidden, but forgot to set it back to that after changing it for some other purpose. The scroll snap in my CSS because the website generally consists of vertically stacked full page sections, so I wanted the scroll to snap to those vertical sections (you can get a better idea of what I'm saying if you visit the WIP website with placeholder text). I thought the scrollTrigger snap only applied to the animation and so wouldn't affect the page scrolling outside of this component, but I might be wrong. Is it bad to have both the CSS scroll snap and the scrollTrigger snapping? The animation seems to work with both included.

 

9 hours ago, Rodrigo said:

In the case of the toggleClass, is doing exactly what you're telling it to do, is adding the ".active" class with the dot in it so your HTML looks like this:

class="_src_components_ExperiencePage_ExperiencePage_module__container .active"

Thanks again. I fixed that but now noticed it applied the class to the container, and not the elements within the container (the elements being animated). Is there a way for me to set as targets the elements within, not the container? I was hoping the active class would be applied only to the element currently snapped to, but I'm not sure if that's possible. 

 

9 hours ago, Rodrigo said:

Finally if you log in your getWidth you'll see that is ran after you resize the window so that is also working as expected:

I see now it runs but the locations of the components, as well as where the scrollTrigger snaps to, are incorrect and are translated an excessive an amount as if the width variable hasn't been updated in the animation. 

 

Another issue I noticed but forgot to mention is the 'section2' element in ExperiencePage.tsx has its opacity increase then decrease correctly in the forward direction of the animation, but when scrolling back, the opacity stays fixed at 1.0. This issue isn't present in the section1 and section3 components. Would you happen to know why please?

 

Link to comment
Share on other sites

13 hours ago, seifhadaba said:

The scroll snap in my CSS because the website generally consists of vertically stacked full page sections, so I wanted the scroll to snap to those vertical sections (you can get a better idea of what I'm saying if you visit the WIP website with placeholder text). I thought the scrollTrigger snap only applied to the animation and so wouldn't affect the page scrolling outside of this component, but I might be wrong. Is it bad to have both the CSS scroll snap and the scrollTrigger snapping? The animation seems to work with both included.

Yeah the snap applies only to that specific ScrollTrigger instance, but that doesn't mean that you can't create other ScrollTrigger instances and snap those as well, you just have t be very careful about the order those ScrollTrigger instances are created. Now if everything is working as expected no need to change anything.

 

13 hours ago, seifhadaba said:

I fixed that but now noticed it applied the class to the container, and not the elements within the container (the elements being animated). Is there a way for me to set as targets the elements within, not the container? I was hoping the active class would be applied only to the element currently snapped to, but I'm not sure if that's possible. 

You can use a call method in your timeline in order to toggle the class on those sections:

https://gsap.com/docs/v3/GSAP/Timeline/call()

.call(() => {
  section1.current.classList.toggle("active");
})

That is just for the first one, but the concept is the same for the rest. You can use the position parameter to adjust the timing so it works the way you intend.

13 hours ago, seifhadaba said:

Another issue I noticed but forgot to mention is the 'section2' element in ExperiencePage.tsx has its opacity increase then decrease correctly in the forward direction of the animation, but when scrolling back, the opacity stays fixed at 1.0. This issue isn't present in the section1 and section3 components. Would you happen to know why please?

That's because you have two consecutive fromTo instances affecting the same properties on the same element, so you have to tell GSAP to not render that second instance yet (scroll to the bottom of the page):

https://gsap.com/docs/v3/GSAP/Timeline/fromTo()

.addLabel("section 2 start")
  .fromTo(
  section2.current,
  { opacity: 0.1, scale: 0.7 },
  { opacity: 1, scale: 1, duration: duration },
  "<"
)
  .fromTo(
  section2.current,
  { opacity: 1, scale: 1 },
  {
    opacity: 0.1,
    scale: 0.7,
    duration: duration,
    immediateRender: false, // <- HERE
  },
  ">"
)

 

Hopefully this helps.

Happy Tweening!

Link to comment
Share on other sites

3 hours ago, Rodrigo said:

That's because you have two consecutive fromTo instances affecting the same properties on the same element, so you have to tell GSAP to not render that second instance yet (scroll to the bottom of the page):

https://gsap.com/docs/v3/GSAP/Timeline/fromTo()

.addLabel("section 2 start")
  .fromTo(
  section2.current,
  { opacity: 0.1, scale: 0.7 },
  { opacity: 1, scale: 1, duration: duration },
  "<"
)
  .fromTo(
  section2.current,
  { opacity: 1, scale: 1 },
  {
    opacity: 0.1,
    scale: 0.7,
    duration: duration,
    immediateRender: false, // <- HERE
  },
  ">"
)

Thanks for the suggestions. I've added this argument as you suggested but now it stays at an opacity of 0.1 instead? It stays there even after the animation has snapped to the component. 

 

17 hours ago, seifhadaba said:
On 4/1/2024 at 4:40 PM, Rodrigo said:

Finally if you log in your getWidth you'll see that is ran after you resize the window so that is also working as expected:

I see now it runs but the locations of the components, as well as where the scrollTrigger snaps to, are incorrect and are translated an excessive an amount as if the width variable hasn't been updated in the animation. 

Also wanted to follow up please on this point from my previous message. I've attached a screenshot showing how section2 is halfway off screen and section 3 is visible on the right to illustrate what I mean. This is in a snap position. 

Screenshot 2024-04-02 at 7.39.48 PM.png

Link to comment
Share on other sites

Hi,

 

I see you're using two different timelines in your main timeline, maybe you should take a look at ScrollTrigger's Container Animation feature:

https://gsap.com/blog/3-8/#containeranimation

 

Also instead of using so many fromTo instances, just set the initial styles using a set instance before creating the timeline, this for transforms like scale. Also you can set the initial opacity using CSS, that should simplify your setup quite a bit and probably give you an easier time. Sometimes from and fromTo instances can be tricky if not handled properly.

 

Happy Tweening!

Link to comment
Share on other sites

15 hours ago, Rodrigo said:

I see you're using two different timelines in your main timeline, maybe you should take a look at ScrollTrigger's Container Animation feature:

https://gsap.com/blog/3-8/#containeranimation

Thanks for sharing, it seems like what I'm looking for. From reading the text in the demo, it seems to suggest snapping to the individual sections in the container animation isn't possible? This is important for me so just wanted to confirm please.

 

Thanks for the suggestion to set the initial properties in css then animate the final properties - that makes sense.

 

Can I please ask again about the issue I'm having with the animation not working when the window is resized? I've provided a screenshot in my last response.

Link to comment
Share on other sites

1 hour ago, seifhadaba said:

Can I please ask again about the issue I'm having with the animation not working when the window is resized? I've provided a screenshot in my last response.

I fiddled with your demo in full view and can't seem to replicate the problem when resizing, maybe you can provide some particular steps in order to reproduce the issue.

1 hour ago, seifhadaba said:

From reading the text in the demo, it seems to suggest snapping to the individual sections in the container animation isn't possible?

You can't snap the actual animations in the container one, but you can definitely snap the horizontal motion, keep in mind that these are two different ScrollTrigger instances.

 

Happy Tweening!

Link to comment
Share on other sites

18 hours ago, Rodrigo said:

I fiddled with your demo in full view and can't seem to replicate the problem when resizing, maybe you can provide some particular steps in order to reproduce the issue

I've recorded a video demonstrating the issue here.

 

1. I open the sandbox then go into full view in a new tab.

2. The animation works correctly initially, with the horizontally scrolling containers being of the right width.

3. I resize the window, the components resize correctly, but the ScrollTrigger no longer snaps at the correct locations, and scrolls beyond the end of the components, as if the width variable hasn't updated. 

4. If i maximise the window again, the animation works correctly

 

I should also note if I refresh the page in the smaller window, the ScrollTrigger width updates correctly. This issue occurs on Safari, Chrome, and Firefox on different computers, and also when locally hosting the website. As you noted before, the getWidth() function is run when the window is resized, but the animation doesn't seem to be updating correctly. 

Link to comment
Share on other sites

Hi,

 

This seems more related to the calculations in your getWidth method rather than anything else. I forked your demo and removed all the ScrollTrigger code and got this:

https://codesandbox.io/p/sandbox/gsap-scrolltrigger-forked-29wfjr?file=%2Fsrc%2Fcomponents%2FExperiencePage%2FExperiencePage.tsx%3A122%2C57

 

If you resize the window you'll see that the animation looks odd, again most likely because of the calculations in the getWidth method. I'd recommend you a different approach for this, perhaps make each slide the width of the screen or the section container and scale up/down the child elements of that slide.

 

I did check if using revert instead of kill works and it seems it does:

https://codesandbox.io/p/sandbox/gsap-scrolltrigger-forked-r5q7k8?file=%2Fsrc%2Fcomponents%2FExperiencePage%2FExperiencePage.tsx

 

The issue I see with this approach is that you'd need to kill the ScrollTrigger instance as well and create everything again. Probably would be a good idea to debounce the resize event handler to avoid executing that code on every resize.

 

Still I'm in favor of the first approach, try to simplify this as much as possible, create the animation without ScrollTrigger and without that getWidth method and make sure that is responsive, then add ScrollTrigger to the mix.

 

Hopefully this helps.

Happy Tweening!

Link to comment
Share on other sites

On 4/4/2024 at 11:32 PM, Rodrigo said:

f you resize the window you'll see that the animation looks odd, again most likely because of the calculations in the getWidth method. I'd recommend you a different approach for this, perhaps make each slide the width of the screen or the section container and scale up/down the child elements of that slide.

Sorry for the late reply. Thanks for the suggestion. Just to be sure, do you mean use padding instead of margins to make the slide fill the whole width of the page, as opposed to having it be less than the full width with margin filling up the rest?

 

On 4/4/2024 at 11:32 PM, Rodrigo said:

I tried opening this to see what you mean but it says the sandbox isn't found. Are you able to please send another link?

Link to comment
Share on other sites

6 hours ago, seifhadaba said:

Sorry for the late reply. Thanks for the suggestion. Just to be sure, do you mean use padding instead of margins to make the slide fill the whole width of the page, as opposed to having it be less than the full width with margin filling up the rest?

Couldn't really tell you TBH. We don't have the time resources to solve HTML/CSS issues for our users, is beyond the scope of what we don in these free forums.

 

Finally I forgot to set the sandbox permissions sorry. By default forks are set as drafts and are private. I updated it and it should be public now:

https://codesandbox.io/p/sandbox/gsap-scrolltrigger-forked-r5q7k8?file=%2Fsrc%2Fcomponents%2FExperiencePage%2FExperiencePage.tsx

 

Happy Tweening!

Link to comment
Share on other sites

5 hours ago, Rodrigo said:

Couldn't really tell you TBH. We don't have the time resources to solve HTML/CSS issues for our users, is beyond the scope of what we don in these free forums.

No problem. Thanks for all the help.

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