j0hannes Posted July 2, 2022 Share Posted July 2, 2022 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) 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: 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 More sharing options...
GSAP Helper Posted July 2, 2022 Share Posted July 2, 2022 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 More sharing options...
mvaneijgen Posted July 2, 2022 Share Posted July 2, 2022 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` 2 Link to comment Share on other sites More sharing options...
GreenSock Posted July 2, 2022 Share Posted July 2, 2022 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. 2 Link to comment Share on other sites More sharing options...
j0hannes Posted July 2, 2022 Author Share Posted July 2, 2022 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 🤷♂️ Link to comment Share on other sites More sharing options...
j0hannes Posted July 2, 2022 Author Share Posted July 2, 2022 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 More sharing options...
GreenSock Posted July 2, 2022 Share Posted July 2, 2022 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? 1 Link to comment Share on other sites More sharing options...
SteveS Posted July 2, 2022 Share Posted July 2, 2022 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. 3 Link to comment Share on other sites More sharing options...
j0hannes Posted July 3, 2022 Author Share Posted July 3, 2022 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 More sharing options...
SteveS Posted July 3, 2022 Share Posted July 3, 2022 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 More sharing options...
GreenSock Posted July 3, 2022 Share Posted July 3, 2022 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 More sharing options...
j0hannes Posted July 3, 2022 Author Share Posted July 3, 2022 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 More sharing options...
j0hannes Posted July 3, 2022 Author Share Posted July 3, 2022 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 More sharing options...
GreenSock Posted July 3, 2022 Share Posted July 3, 2022 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 More sharing options...
j0hannes Posted July 4, 2022 Author Share Posted July 4, 2022 Is there a Codesandbox project with React and Scroll Trigger that I can riff of on? Link to comment Share on other sites More sharing options...
SteveS Posted July 4, 2022 Share Posted July 4, 2022 @j0hannes I don't have a CSB but here is a github repo I put together to help people out with this exact issue: https://github.com/StevenStavrakis/react-scrolltrigger-example 1 Link to comment Share on other sites More sharing options...
GreenSock Posted July 4, 2022 Share Posted July 4, 2022 Here's our React starter: https://codesandbox.io/s/gsap-react-starter-ut42t And with bonus plugins (trial): https://codesandbox.io/s/gsap-bonus-plugins-react-starter-je6ln Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now