Jump to content
Search Community

Run animation after render and on set scrollTrigger

Tomas100 test
Moderator Tag

Go to solution Solved by GreenSock,

Recommended Posts

Hi 

I have an animation set to run on ScrollTriger but I would like it to run when page loaded as well.

It works in strict mode in react but not in production or when strict mode is disabled.  
I would like to start animation after the page has loaded and on set ScrollTrigger.
How can I do that? 
Thank you !

 

useLayoutEffect(() => {
const tl: gsap.core.Timeline = gsap.timeline({
scrollTrigger: {
trigger: props.navbar,
start: "center top",
toggleActions: "reverse none none play",
},
});
let ctx: gsap.Context = gsap.context(() => {
tl.to(props.navLink, {
x: "0",
autoAlpha: 1,
duration: 1,
stagger: 0.2,
delay: 0.2,
});
}, props.navbar);
 
tl.play()
 
 
return () => ctx.revert();
}, []);
Link to comment
Share on other sites

One thing that's definitely wrong is that you're creating your timeline OUTSIDE of the gsap.context() which totally defeats the purpose. Make sure you move that inside so that it gets cleaned up (reverted) when you call ctx.revert(). 

 

If you still need help, please make sure you provide a minimal demoPlease don't include your whole project. Just some colored <div> elements and the GSAP code is best.

 

Here's a React starter template that you can fork.

 

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

Thx for the answer!

I have added timeline inside the gsap.context and now I am getting the following error  "Element not found:".

Small demo here https://stackblitz.com/edit/react-oflp9n?file=src%2Fstyle.css,src%2FApp.js,src%2FuseAnim.js

The example acts differently than my actual app as in the example the animation works after loading and not triggering when header`s center is in the top of the screen. In my actual app its the opposite the animation is not playing after render but working properly when the screen is scrolled up and down. Both have the same error  "Element not found:"

 

Thank you!

Link to comment
Share on other sites

The problem is that you passed in ".section" as the scope for your gsap.context() which basically means "for all selector text inside this gsap.context(), only find elements that are descendants of '.section'" but then you use ".section" as your trigger. So basically you're saying "find elements with the class of 'section' that are descendants of the element with the class of 'section'" There are no '.section' elements inside '.section' :)

 

It's a bit odd to pass selector text in as the scope, by the way. One of the key concepts with React is to make things modular and they encourage you to use Refs because they're guaranteed unique whereas if you use selector text, what happens if there are 10 instances of your React component on the page? They'll all find elements inside each other. Like if you have 3 ".box" elements inside each one, and you do a document.querySelectorAll(".box"), you'll get 30 of them. 

 

So in React, it's more common to use a Ref for the scope, not selector text. If you haven't done so already, I would strongly recommend reading this article:

 

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

 

I hope that helps.

  • Like 1
Link to comment
Share on other sites

Thank you Jack for your help!

 

In your demo everything works perfectly and I have adjusted the code in my project by it, sadly i still can not make the animation run after render. I have no idea why its happening if I enable strict mode everything works perfectly, if I place setTimeout(() => { tl.play()}, 2000) after render and timeout all the elements just appear instantly. 
Any ideas? Thank you!

 

 

Link to comment
Share on other sites

That certainly sounds like a cleanup issue, but it's impossible for us to troubleshoot blind, sorry. Is there any way you can provide a Stackblitz that illustrates the problem? There must be something different between the Stackblitz demo and wherever you're having the problem. It boils down to "stop the difference" at this point I guess. 

Link to comment
Share on other sites

I can not recreate the issue in the Stackblitz.  have checked the versions of gsap and react and they are the same, even took the code from the working Stackblitz and placed in my project and its not animating after render. 
Can there be something with scrollTrigger? If I remove scrolltrigger the animation runs after render but of course it does not work on scroll.

Link to comment
Share on other sites

I really wish I could help more, but if I can't recreate the issue or see it anywhere, it makes it pretty much impossible. At this point, it still smells like a cleanup issue to me, like you're creating duplicate conflicting ScrollTriggers maybe. Did you put a console.log() right before your GSAP/ScrollTrigger code to see how many times it's getting called? React 18 calls useEffect()/useLayoutEffect() TWICE in strict mode (which is the default in development but not production), so perhaps you're inadvertently duplicating things? If you're using gsap.context() properly like in my demo, then you shouldn't need to worry at all (assuming you're calling revert() on the context in your cleanup function).  

 

So I'd recommend some console.log() calls to see if things are firing in the order (and the number of times) you expected. I'd also recommend turning on markers in your ScrollTrigger just to see if it's what you expected. Like if you see several additional markers, that's another clue that maybe you're accidentally creating duplicates/conflicts somewhere. 

  • Like 1
Link to comment
Share on other sites

Thank you Jack again for trying to help!

 

I have disabled the strict mode  and I am having this issue. With strict mode on everything works as intended. 

Now i started disabling parts of the project one by one and I found that when I disable ScrollSmoother all animations are fully functional.

Probably i have something wrong in my ScrollSmoother set up which is misbehaving.

 

this is my set up

 

import "./App.scss";
import Header from "./layouts/Header";
import { Route, Routes } from "react-router-dom";
import AboutUs from "./pages/AboutUs";
import SideNav from "./components/navBars/SideNav";
import { useEffect } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { ScrollSmoother } from "gsap/dist/ScrollSmoother";
import Footer from "./layouts/Footer";
import MobileNav from "./components/navBars/MobileNav";

gsap.registerPlugin(ScrollTrigger, ScrollSmoother);

function App(): JSX.Element {
  useEffect(() => {
    let smoother: globalThis.ScrollSmoother = ScrollSmoother.create({
      smooth: 2, // seconds it takes to "catch up" to native scroll position
      effects: true, // look for data-speed and data-lag attributes on elements and animate accordingly
      wrapper: "#smooth-wrapper",
      content: "#smooth-content",
      speed: 0.5,
    });
  }, []);

  return (
    <div className="App" id="App">
      <SideNav />
      <MobileNav />
      <div id="smooth-wrapper">
        <div id="smooth-content">
          <Header />
          <Routes>
            <Route path="/" element={<AboutUs />} />
          </Routes>
          <Footer />
        </div>
      </div>
    </div>
  );
}

export default App;

Any ideas ? 
Thank you!

Link to comment
Share on other sites

Hi,

 

As far as I know, you shouldn't disable strict mode since it works only on development. With that out of the way, I don't see anything that could create an issue without strict mode. With strict mode you should have issues because of the double effect call.

 

If possible please provide a minimal demo that illustrates the situation.

 

Happy Tweening!

Link to comment
Share on other sites

Yeah, that's pretty weird if you only have things work in strict mode which is likely double-calling things. 🤷‍♂️

 

Just out of curiosity, does it help anything if you do proper cleanup of your ScrollSmoother? 

 useEffect(() => {
    let smoother: globalThis.ScrollSmoother = ScrollSmoother.create({
      smooth: 2, // seconds it takes to "catch up" to native scroll position
      effects: true, // look for data-speed and data-lag attributes on elements and animate accordingly
      wrapper: "#smooth-wrapper",
      content: "#smooth-content",
      speed: 0.5,
    });
   return () => smoother.kill(); // <- CLEANUP!
  }, []);

 

And you're saying it's impossible to recreate this issue anywhere else, like in a minimal demo in Stackblitz, CodeSandbox, CodePen, etc.? 

 

What if you don't use ScrollSmoother at all? Does that solve anything? 

Link to comment
Share on other sites

Heya! so I'm not certain exactly what's going on under the hood here. I may need to tag in @greensock for a proper explanation.

What I do know is that your ScrollSmoother is meant to be defined first - before any other timelines are created.

In your demo React is creating the timeline first, then the ScrollSmoother - If you check out this simplified pen you can see that if you create the smoother first it works a ok, alternately if you call .play() after the smoother's created it works. 


See the Pen bGxpjpV?editors=1111 by GreenSock (@GreenSock) on CodePen


It's just the order that's messing it up, whether this is a bug or just an order that needs to be respected I don't know. I tried looking into sort and refreshPriority but that doesn't help here.
 

  • Like 2
Link to comment
Share on other sites

  • Solution

Yes, the issue is that you create your ScrollTriggers and they use the default viewport as their "scroller" that they're watching, and THEN you create you ScrollSmoother which changes the default scroller to be itself (well, its wrapper element). So those previously-created ScrollTriggers are looking at the wrong scroller to update their positions. 

 

The next release of ScrollSmoother automatically checks for that condition and resolves it, but in the meantime I created this helper function for you: 

function safeSmoother(vars) {
  let wrapper = gsap.utils.toArray(vars.wrapper || document.body)[0],
      existingScrollTriggers = ScrollTrigger.getAll().filter((st) => st.scroller === window || st.scroller === wrapper);
  existingScrollTriggers.forEach((st) => st.revert(true, true));
  let smoother = ScrollSmoother.create(vars);
  existingScrollTriggers.forEach((st) => {
    st.vars.scroller = smoother.wrapper();
    st.revert(false, true);
    st.init(st.vars, st.animation);
  });
  return smoother;
}

You just use that instead of ScrollSmoother.create(). It returns the smoother instance. Here's a fork of your demo: 

https://codesandbox.io/s/nostalgic-danilo-orycgd?file=/src/App.js

 

Does that resolve things for you? 

  • Like 2
Link to comment
Share on other sites

Thank you guys for your help!
 

Jacks solution is working perfectly!  

The issue i had left 😅 was that in my project, after render, animation was almost finished, so I had to put more delay but then it was too much delay for the scrolltrigger.
The Solution I found thanks to Cassie explanation was to pass info into react context when ScrollSmoother  was set and then set scrolltrigger afterwards
I have made a demo here https://codesandbox.io/s/falling-architecture-81tk2n?file=/src/App.js

It seems to work good but it would be nice if somebody could take a look if I am not imagining some stuff which later is gonna create more problems :)
Thank you!

Link to comment
Share on other sites

I'm glad to hear it's working! 🥳

 

If you still need help, would you mind clarifying exactly what you need? What is your specific question? I'm definitely not a React guy, so if you're asking for someone to look over your whole React project and give you a code review, I can't offer you that and I'm not sure anyone else here would be willing. These forums are really intended for GSAP-specific questions. Got any of those? 

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