Jump to content
Search Community

numerical scrub has no effect

greenchonies test
Moderator Tag

Go to solution Solved by greenchonies,

Recommended Posts

I am using gsap scrolltrigger in a nextjs app. I have set up the scroll animation and everything is working except the numerical scrub value has no effect. No matter what number I put in, it acts as if it was just scrub: true.

 

Here is how I've set it up. In my main page:

import { useRef } from 'react'
import { gsap } from 'gsap'
import { useGSAP } from '@gsap/react'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import { ScrollToPlugin } from 'gsap/ScrollToPlugin'
import { introAnimation, masterScroll } from '@/animations'

const scope = useRef<HTMLElement>(null)
const tl = useRef<gsap.core.Timeline>()

useGSAP(
    () => {
      gsap.registerPlugin(ScrollTrigger, ScrollToPlugin)

      tl.current = gsap
        .timeline()
        .add(introAnimation())
        .eventCallback('onComplete', () => {
          masterScroll(scope)
        })
    },
    { scope: scope }
  )
  
  return (
    <main ref={scope}>
      ...
    </main>
  )

Then in animations.ts:

import { gsap } from 'gsap'
import { RefObject } from 'react'

export const masterScroll = (trigger: RefObject<HTMLElement>) => {
  const tl = gsap.timeline({
    scrollTrigger: {
      trigger: trigger.current,
      start: 'top top',
      end: '+=900%',
      scrub: 1,
      pin: true,
    },
  })
  
  // tl. animations
  
  return tl
  
  }

The scroll animations are working, but the scrub value has no effect. There is  no smoothing whatsoever. What am I missing?

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

Yeah, we can't do much to help without a minimal demo, but make sure you're not nesting ScrollTriggers inside a parent timeline. That logically can't work because the playhead can either be controlled by the parent timeline -OR- the scrollbar's position, not both. They could be going in completely different directions. See what I mean? So if you're going to apply a ScrollTrigger to an animation, it should be at the very top level (parent timeline). 

Link to comment
Share on other sites

Hm, are you saying you used Lenis instead of ScrollSmoother? I didn't see any ScrollSmoother in your sample code. I'm still curious what was going on and I cannot imagine why using Lenis would solve it. Glad you got it working, but we'd be happy to look at a minimal demo to figure out what was going on previously. 

Link to comment
Share on other sites

I was not using ScrollSmoother. All of the animations were within absolutely positioned sections within the viewport, so using 'scrub: 1' would be enough to give the effect of a smooth scroll.
 

I know this works because I got it to work in an Astro project. But when I ported the project to NextJS, the numerical scrub just behaved like 'scroll: true'. So my quick fix was to install Lenis as that made the scroll animations smooth with the added benefit of making any page scroll smooth even without gsap involved.

 

If I have some extra time I’ll make a minimal demo, and maybe that will help me find the issue. But for now Lenis is doing the trick.

Link to comment
Share on other sites

I discovered what was causing numerical scrub not to work. It turns out that even though I was registering the scrolltrigger plugin in the parent timeline, I needed to import and register scrolltrigger in the child timeline as well. Weird because scrub was working without doing this, but numerical scrub didn't work until I did this. The only side effect is that now I get this warning in the console from nextjs:

Warning: Extra attributes from the server: style
    at body
    at html

I decided not to use Lenis because it was conflicting with scroll snapping. I needed the timeline to snap to labels. Luckily my entire page is a gsap animation inside of a pinned container soscrub: 1 is enough to give the effect of a smooth scroll without the need for scrollsmoother.

Link to comment
Share on other sites

Quote

It turns out that even though I was registering the scrolltrigger plugin in the parent timeline, I needed to import and register scrolltrigger in the child timeline as well.

This doesn't sound right to me.  It would be great to see what your timeline setup is. "Child timeline" Is making me think that you have nested ScrollTriggers (Like Jack mentioned)

 

But just going off the code snippets you've posted, you're doing something like this?

let tl = gsap
  .timeline()
  .to(".box", {
    rotation: 360
  })
  .eventCallback("onComplete", () => {
    masterScroll();
  });

const masterScroll = () => {
  const tl = gsap
    .timeline({
      scrollTrigger: {
        trigger: ".trigger",
        start: "top top",
        end: "bottom top",
        scrub: 3,
        markers: true
      }
    })

    .to(".box-2", {
      rotation: 800
    });
};

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


This is a strange setup, you're instantiating the second scrubbed timeline when the first timeline finishes. This is at a specific *time* - but as it's a ScrollTriggered timeline it's start is controlled by scroll position. This sounds like it's asking for conflicts and possibly just unnecessary. What are you trying to accomplish or avoid by doing this? Maybe we can help?

Also - seeing as you're using React, this pattern raises some flags for me - The second "masterScroll" timeline isn't contextSafe as it's not created inside the useGSAP function on load, so it won't be cleaned up properly either.

Pretty certain we could advise a better way of writing this if we saw a demo!

If you'd like some eyes on it - Here's a React starter template that you can fork to create a minimal demo.

  • Like 1
Link to comment
Share on other sites

I would love some some insight into how I could go about this in a better way. The reason I have an intro animation that calls a scroll animation on complete is because the intro animation is a page loading animation. When I land on the page, I need to see the intro animation, and only after the intro is finished should I be able to scroll to scrub the rest of the page animations. Another reason I had to do it this way is that the elements in the page loading animation need to be animated out of the view once the scrolling begins. Here is a minimal example

https://stackblitz.com/edit/react-gf8jgn?file=src%2FApp.js

 

I am adding child timelines to a main timeline to keep my code organized. The masterScroll animation is very long and so I moved it to it's own file along with the intro animation.

 

I am wrapping my page with an AnimationWrapper component because I need to fetch some data on the main page, and I can't do that in a client component in nextjs.

 

As you can see, the scrubbing works, but the numerical scrub amount has no effect, and snapping does not work.

  • Like 2
Link to comment
Share on other sites

Hi,

 

This seems to work:

export default function AnimationWrapper({ children }) {
  const main = useRef();
  const tl = useRef();

  useGSAP(
    (context, contextSafe) => {
      tl.current = gsap
        .timeline({
          onComplete: contextSafe(() => masterScroll(main)),
        })
        .add(introAnimation());
    },
    { scope: main }
  );
  return (
    <div ref={main} className="animation-wrapper">
      {children}
    </div>
  );
}

It's cleaner and makes more sense from a GSAP+React point of view, at least IMHO.

 

Also you can fetch data on client components in Next, check the docs:

https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#fetching-data-on-the-client-with-route-handlers

 

Hopefully this helps.

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

Online editors like Stackblitz, CodeSandbox, etc. don't seem to unify the packages, so if the @gsap/react has a dependency on "gsap" it loads that separately and then the normal "gsap" package you're using in the project is treated as a separate thing (so 2 distinct GSAP objects). Therefore, the context that gets created inside your useGSAP() is from the gsap that's loaded with useGSAP which is DIFFERENT than the one that's creating your actual animations.  This is why it's best to register the useGSAP hook as a plugin: 

gsap.registerPlugin(useGSAP, ScrollTrigger);

(do that BEFORE you use the useGSAP() hook of course).

 

Does that resolve things for you?

 

3 hours ago, Cassie said:

If I move registerPlugin inside the useGSAP hook and then hard refresh the scrub and snapping stops working

Yeah, that's because if you do it inside the useGSAP and you haven't registered useGSAP already, the "gsap" that creates the context is not the same one as the gsap that's creating your tweens/ScrollTriggers, as explained above. And again, this is almost never required in a "real" project because those should unify the packages during the build process. But it's still totally fine to register the useGSAP hook in any case. 

 

In summary: Is suspect it'll solve everything for you if you just gsap.registerPlugin(useGSAP, ScrollTrigger)

  • Like 3
Link to comment
Share on other sites

  • Solution

Looks like registering the plugins outside of useGSAP did the trick, thank you! I remember I moved the plugin registration into the useGSAP because I was getting the warning Extra attributes from the server: styleAnd found this thread, which suggested to register inside a useEffect: 

 

 

I also hadn't considered to register useGSAP as a plugin as well. Here is the updated demo with your suggestions:

https://stackblitz.com/edit/react-egkdv7?file=src%2FAnimationWrapper.js

 

Putting the onComplete inside the parent timeline is indeed cleaner, but the only issue is I'm using typescript and the onComplete event and contextSafe function now give me the following errors:

 

onComplete: Type '() => gsap.core.Timeline' is not assignable to type 'Callback'.
  Type 'Timeline' is not assignable to type 'void'.

 

contextSafe: Cannot invoke an object which is possibly 'undefined'.

 

Here is my typescript setup: https://stackblitz.com/edit/react-jjrktj?file=src%2FAnimationWrapper.tsx

(I don't get the same errors in stackblitz)

 

When I use my previous eventCallback method, I get no errors. But it looks like callbacks in useGSAP are context safe anyway, so is there no need for me to use contextSafe? Forgive my ignorance, just tring to understand this more. Demo:

https://stackblitz.com/edit/react-oxxh2h?file=src%2FAnimationWrapper.js

Link to comment
Share on other sites

On 2/23/2024 at 5:20 PM, greenchonies said:

When I use my previous eventCallback method, I get no errors. But it looks like callbacks in useGSAP are context safe anyway, so is there no need for me to use contextSafe?

Yes, that's exactly correct. 

 

Good job getting it solved. 🙌

  • Like 1
Link to comment
Share on other sites

  • 1 month later...
37 minutes ago, Rodrigo said:

Hey @NickWoodward,

 

Since you already created a thread where we're discussing this, let's better focus our attention to that thread, ok?

 

Happy Tweening!

Hi Rodrigo, I wrote this before I made that thread (but after the scoping one?), but I agree! Been enjoying the help (and useGSAP more generally tbh), thanks

Link to comment
Share on other sites

19 hours ago, greenchonies said:

Instead of using onComplete as an option inside the timeline, I added a .eventCallback like this:

https://stackblitz.com/edit/react-gf8jgn?file=src%2FAnimationWrapper.js

Thanks @greenchonies. I was actually talking about the TS error (but maybe that's how you fixed it). Either way I got it sorted thanks. Cool animation btw, I forgot to mention at the time :)

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