Jump to content
GreenSock

j0hannes

Is there a way to delay the GSAP Scroll trigger start and end-value calculations?

Recommended Posts

I have a landing page that utilises the css "zoom" value (to scale it's content based on the screen-size).

 

It takes a brief moment to calculate the correct zoom value.

This happens in  a useLayoutEffect on the landing page component. So right before it renders.

 

I think a side effect of this is that the hight of the entire page isn't entirely reliable in the first few milliseconds when the page is loaded.

 

This assumption would explain the problem I'm having:

 

The animation starts too early or too late, depending on where I am inside the page when I hit refresh.

I have the markers turned on, that signal me the start and the end of the animation. Their position is too far down or too far up. Depending on where I am, when I do the refresh (refreshing when I'm at the top of the page will bring the start and end values too far down)

The start and end values rely on the hight of the entire webpage, right? 

And I think the zooming process changes the hight.

 

An attempt at solving this was to  delay the rendering of the component that houses the animation (and thus the gsap code)
image.png.2863f736dd003c9cb5ac24129efd59ea.png

 

So maybe then GSAP does its internal calculations later, when the hight of the webpage is reliable.

But this didn't solve my problem.

 

Maybe there is another way to delay GSAP doing its thing?

 

Or maybe my assumptions are wrong and something else is causing the problem?

 

You probably want a look at the code of the animation, so here is a snippet:

image.thumb.png.30f7239eb21f0118c2cf60c73eb1c4c2.png

 

there are may different elements animated this way.

 

(except for the iPhone image), their values are:

start: "-=10%"

end: "+=60%"

 

(experimenting with the values (eg by using px values) is fruitless)

Here is a video, where I show it in action

 

https://www.awesomescreenshot.com/video/9767109?key=36310e15cbe8f26b1514abfc8031bca9

 

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

Hi @j0hannes welcome to the forum!

 

It seems like you know what you're talking about and your explanation is really detailed, but we're missing a minimal demo. I would love to see this edge case in something like Codepen or Codesandbox with just some colored DIVs , I don't think you'll need to use React to reproduce this issue.

 

The problem with not having a minimal demo is that any one who wants to help you needs to create the demo by them selfs, before they can even think about fixing the issue. 

 

Sidenote: you can animate multiple elements at once with ScrollTrigger, if you create a timeline instead of only using gsap.to(), see the docs  under "Advanced example". This way you don't have to add multiple triggers to the same elements as seen on element `.PlasmicBringItAllTogetherSection_root`

  • Like 2
Link to comment
Share on other sites

Agreed, a minimal demo is essential but if you want to test your theory about the page changing AFTER ScrollTrigger calculates the start/end positions, you could try a setTimeout() to run a full second or two later and call ScrollTrigger.refresh() just as a test. This isn't for final deployment or anything - just testing your theory. ScrollTrigger automatically listens for the "load" event on the window and calls ScrollTrigger.refresh() but obviously if your app is making layout changes after initial load, you must tell ScrollTrigger to refresh() after you're done. 

  • Like 2
Link to comment
Share on other sites

I'm sorry, but I can't recreate it.

I think the best I can do is to iterate the fact, removing the logic that changes the "--scalefactor" that represents the zoom value takes care of the issue (as shown in the video) .

... but I can't get rid of the scale-factor. Otherwise the site wouldn't be responsive.

 

Is there any way to delay GSAP doing its thing?

I've tried the useLayoutEffect as seen above.

I've also tried conditionalising the useEffect, that houses the GSAP Logic with a pageHeightIsSteady variable, that is set true, right after I call: 

const scaleFactor = currentBodyWidth / baseWidth
body.style.setProperty('--scalefactor', (scaleFactor < 1) ? scaleFactor.toString() : "1")

 

// GSAP Scroll Animation
React.useEffect(() => {
  if (pageHeightIsSteady) {
    //@ts-ignore
    gsap.registerPlugin(ScrollTrigger);
    gsap.to(".contentCardLeft", {
      x: 500,
      y: 500,

      scale: 0.6, // scale of card
      scrollTrigger: {
        trigger: ".PlasmicBringItAllTogetherSection_root",
        start: `-=${animationStartValues.iconAnimationStart}%`,
        end: `+=${animationEndValues.iconAnimationEnd}%`,
        markers: true,
        scrub: 1, // animation - max speed bound
        // scrub: true // speed of animation equal to scroll speed
      },
    });
    // Email
    gsap.to(".sourceIconMail", {
      x: 580,
      y: 650,

      scrollTrigger: {
        trigger: ".PlasmicBringItAllTogetherSection_root",
        start: `-=${animationStartValues.iconAnimationStart}%`,
        end: `+=${animationEndValues.iconAnimationEnd}%`,
        scrub: 1, // animation - max speed bound
        // scrub: true // speed of animation equal to scroll speed
      },
    });
    // YouTube
    gsap.to(".sourceIconYoutube", {
      x: 450,
      y: 650,

      scrollTrigger: {
        trigger: ".PlasmicBringItAllTogetherSection_root",
        start: `-=${animationStartValues.iconAnimationStart}%`,
        end: `+=${animationEndValues.iconAnimationEnd}%`,
        scrub: 1, // animation - max speed bound
        // scrub: true // speed of animation equal to scroll speed
      },
    });
    // Twitter
    gsap.to(".sourceIconTwitter", {
      x: 250,
      y: 600,

      scrollTrigger: {
        trigger: ".PlasmicBringItAllTogetherSection_root",
        start: `-=${animationStartValues.iconAnimationStart}%`,
        end: `+=${animationEndValues.iconAnimationEnd}%`,
        scrub: 1, // animation - max speed bound
        // scrub: true // speed of animation equal to scroll speed
      },
    });
    // Wifi
    gsap.to(".sourceIconRss", {
      x: 550,
      y: 400,

      scrollTrigger: {
        trigger: ".PlasmicBringItAllTogetherSection_root",
        start: `-=${animationStartValues.iconAnimationStart}%`,
        end: `+=${animationEndValues.iconAnimationEnd}%`,
        scrub: 1, // animation - max speed bound
        // scrub: true // speed of animation equal to scroll speed
      },
    });
    // User
    gsap.to(".sourceIconPodcast", {
      x: 400,
      y: 250,

      scrollTrigger: {
        trigger: ".PlasmicBringItAllTogetherSection_root",
        start: `-=${animationStartValues.iconAnimationStart}%`,
        end: `+=${animationEndValues.iconAnimationEnd}%`,
        scrub: 1, // animation - max speed bound
        // scrub: true // speed of animation equal to scroll speed
      },
    });
    // Pix art
    gsap.to(".sourceIconPinterest", {
      x: 250,
      y: 350,

      scrollTrigger: {
        trigger: ".PlasmicBringItAllTogetherSection_root",
        start: `-=${animationStartValues.iconAnimationStart}%`,
        end: `+=${animationEndValues.iconAnimationEnd}%`,
        scrub: 1, // animation - max speed bound
        // scrub: true // speed of animation equal to scroll speed
      },
    });
    // right side
    // cards
    gsap.to(".contentCardRight", {
      x: -430,
      y: 550,
      scale: 0.6, // scale of card

      scrollTrigger: {
        trigger: ".PlasmicBringItAllTogetherSection_root",
        start: `-=${animationStartValues.iconAnimationStart}%`,
        end: `+=${animationEndValues.iconAnimationEnd}%`,
        scrub: 1, // animation - max speed bound
        // scrub: true // speed of animation equal to scroll speed
      },
    });
    // Vimeo
    gsap.to(".sourceIconVimeo", {
      x: -450,
      y: 670,

      scrollTrigger: {
        trigger: ".PlasmicBringItAllTogetherSection_root",
        start: `-=${animationStartValues.iconAnimationStart}%`,
        end: `+=${animationEndValues.iconAnimationEnd}%`,
        scrub: 1, // animation - max speed bound
        // scrub: true // speed of animation equal to scroll speed
      },
    });
    //  Instagram
    gsap.to(".sourceIconInstagram", {
      x: -550,
      y: 650,

      scrollTrigger: {
        trigger: ".PlasmicBringItAllTogetherSection_root",
        start: `-=${animationStartValues.iconAnimationStart}%`,
        end: `+=${animationEndValues.iconAnimationEnd}%`,
        scrub: 1, // animation - max speed bound
        // scrub: true // speed of animation equal to scroll speed
      },
    });
    // // arrow down
    gsap.to(".sourceIconPocket", {
      x: -650,
      y: 500,

      scrollTrigger: {
        trigger: ".PlasmicBringItAllTogetherSection_root",
        start: `-=${animationStartValues.iconAnimationStart}%`,
        end: `+=${animationEndValues.iconAnimationEnd}%`,
        scrub: 1, // animation - max speed bound
        // scrub: true // speed of animation equal to scroll speed
      },
    });
    //
    gsap.to(".sourceIconReddit", {
      x: -450,
      y: 200,

      scrollTrigger: {
        trigger: ".PlasmicBringItAllTogetherSection_root",
        start: `-=${animationStartValues.iconAnimationStart}%`,
        end: `+=${animationEndValues.iconAnimationEnd}%`,
        scrub: 1, // animation - max speed bound
        // scrub: true // speed of animation equal to scroll speed
      },
    });
    // // stat icon
    gsap.to(".sourceIconFeedly", {
      x: -250,
      y: 450,

      scrollTrigger: {
        trigger: ".PlasmicBringItAllTogetherSection_root",
        start: `-=${animationStartValues.iconAnimationStart}%`,
        end: `+=${animationEndValues.iconAnimationEnd}%`,
        scrub: 1, // animation - max speed bound
        // scrub: true // speed of animation equal to scroll speed
      },
    });

    gsap.to(".sourceIconBookmark", {
      x: -350,
      y: 280,

      scrollTrigger: {
        trigger: ".PlasmicBringItAllTogetherSection_root",
        start: `-=${animationStartValues.iconAnimationStart}%`,
        end: `+=${animationEndValues.iconAnimationEnd}%`,
        scrub: 1, // animation - max speed bound
        // scrub: true // speed of animation equal to scroll speed
      },
    });
    gsap.to(".iPhone", {
      scale: 0.8, // scale of phone
      scrollTrigger: {
        trigger: ".PlasmicBringItAllTogetherSection_root",
        start: `-=${animationStartValues.iphoneAnimationStart}%`,
        end: `+=${animationEndValues.iphoneAnimationEnd}%`,
        scrub: 1, // animation - max speed bound
        markers: {startColor: "purple", endColor: "fuchsia"}
        // scrub: true // speed of animation equal to scroll speed
      },
    });
  }
},[pageHeightIsSteady]);

 

 

You can also have a look at the live page here:

 

https://www.hubhub.app/

 


Sth else, that I want to note is that the two cards often have odd positions - but only on the live page 🤷‍♂️

 

image.thumb.jpeg.0d7f0efc252928fa6436bf1cb4d78798.jpeg

 

 

 

 

 

Link to comment
Share on other sites

 

42 minutes ago, GreenSock said:

Agreed, a minimal demo is essential but if you want to test your theory about the page changing AFTER ScrollTrigger calculates the start/end positions, you could try a setTimeout() to run a full second or two later and call ScrollTrigger.refresh() just as a test. This isn't for final deployment or anything - just testing your theory. ScrollTrigger automatically listens for the "load" event on the window and calls ScrollTrigger.refresh() but obviously if your app is making layout changes after initial load, you must tell ScrollTrigger to refresh() after you're done. 

 

React.useLayoutEffect(() => {
  setTimeout(() => {}, 4000)
  ScrollTrigger.refresh()
})

This is what I was looking for. Thank you

 

however it didn't resolve the issue :/

 

Link to comment
Share on other sites

14 minutes ago, j0hannes said:

 

React.useLayoutEffect(() => {
  setTimeout(() => {}, 4000)
  ScrollTrigger.refresh()
})

This is what I was looking for. Thank you

 

however it didn't resolve the issue 😕

 

No no, that's incorrect. That won't wait any time before firing ScrollTrigger.refresh(). I think you meant: 

 

setTimeout(() => ScrollTrigger.refresh(), 4000);

Right? 

  • Like 1
Link to comment
Share on other sites

Hi @j0hannes , In my experience, it's a much better practice to create GSAP effects for elements inside their component and use Refs whenever possible. Doing this often solves a lot of issues just due to the fact that you make everything more predictable.

Also, you can register your plugins just under your imports, not as an effect of a component. I'd probably make that change as well.

  • Like 3
Link to comment
Share on other sites

9 hours ago, GreenSock said:

No no, that's incorrect. That won't wait any time before firing ScrollTrigger.refresh(). I think you meant: 

 

setTimeout(() => ScrollTrigger.refresh(), 4000);

Right? 

yes that's right. I've actually tried to call the ScrollTrigger.refresh() command from multiple locations. It never had any effect.

 

9 hours ago, SteveS said:

Hi @j0hannes , In my experience, it's a much better practice to create GSAP effects for elements inside their component and use Refs whenever possible. Doing this often solves a lot of issues just due to the fact that you make everything more predictable.

Also, you can register your plugins just under your imports, not as an effect of a component. I'd probably make that change as well.

Thanks. It's now registered under imports. 

 

I've also changed all the gsap.to() commands to target Refs instead of classnames. Both changes had no impact on the start&end markers unfortunately.

And since using Refs the animation doesn't start at all anymore, so I reverted back to the commit I made just before.

 

Speaking of commits - If someone would be kind enough to take a look at the project, I'd happily give a Github invite and a more detailed walkthrough. (And a tip if the issue gets resolved)

---

 

It's something about changing the --scalefactor variable which represents the value of zoom on the root of the landing page that causes this.

I've also tried using a styled-component instead of body.style.setProperty

Inside of the useLayoutEffect of index.tsx.

 

Changing it from:

const scaleFactor = currentBodyWidth / baseWidth
body.style.setProperty('--scalefactor', (scaleFactor < 1) ? scaleFactor.toString() : "1")

To:

setScaleFactor(currentBodyWidth / baseWidth)

Which sets:

const LandingWrapper = styled.div`
  zoom: ${scaleFactor}
`

Didn't improve the situation 😔

Link to comment
Share on other sites

If switching to refs broke something then there is definitely something funky going on. Using refs is the de facto way of selecting dom elements in react.

Link to comment
Share on other sites

10 hours ago, j0hannes said:

yes that's right. I've actually tried to call the ScrollTrigger.refresh() command from multiple locations. It never had any effect.

 

I'm slightly concerned that maybe you haven't actually called it properly after everything is settled. The code you provided earlier definitely did NOT do that correctly. 

 

If you'd like some help, please isolate the issue in a minimal demo, like perhaps in a CodePen or CodeSandbox with only a few colored <div> elements. Please don't provide your whole project, as that is way beyond the scope of help we can provide here (see the forum guidelines). Or you can post in the "Jobs & Freelance" forum to seek paid assistance. 

Link to comment
Share on other sites

 

4 hours ago, SteveS said:

If switching to refs broke something then there is definitely something funky going on. Using refs is the de facto way of selecting dom elements in react.

 

because I forgot adding .current.

Now the animation works with Refs. But the start and end values are still relying on where you refresh the page. 

Link to comment
Share on other sites

8 minutes ago, GreenSock said:

I'm slightly concerned that maybe you haven't actually called it properly after everything is settled. The code you provided earlier definitely did NOT do that correctly

 

If you'd like some help, please isolate the issue in a minimal demo, like perhaps in a CodePen or CodeSandbox with only a few colored <div> elements. Please don't provide your whole project, as that is way beyond the scope of help we can provide here (see the forum guidelines). Or you can post in the "Jobs & Freelance" forum to seek paid assistance. 

whether I called it inside a setTimeout callback or in different parts of the lifecycle, the start-end markers don't seem to care.

Link to comment
Share on other sites

Can you please provide a minimal demo that clearly shows the issue even when you call ScrollTrigger.refresh() after everything settled (no more layout in the DOM)? Don't include your whole project; there are way too many factors involved in live projects. Just some colored <div> elements. I'm relatively confident that there's probably something simple at play here. Like a misunderstanding or you think you're refreshing at the right time but that isn't true. I'm totally guessing, though, because I don't have a minimal demo to look at. 

Link to comment
Share on other sites

Is there a Codesandbox project with React and Scroll Trigger that I can riff of on?

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