Ganbatte Posted December 5, 2023 Share Posted December 5, 2023 Hello! I'm new to the world of GSAP, although I have experience with CSS. I'm using NextJS 14 in my project. I've watched quite a few videos from GSAP, but I haven't found any discussing clearing inline styles with scrub: true. I have an image positioned in the center of the screen using position: absolute, and I've used GSAP animations to make it go fullscreen on scroll. My problem is that when I resize the screen, it simply enlarges to the original size instead of adjusting to the current size. I've tried various units to specify the width for GSAP, such as using percentages (100%), viewport width (100vw), or window.innerWidth, but none seem to work properly for achieving fullscreen. I don't understand this behavior because it seems to convert the inline style to pixels even when I specify percentages. I've seen in multiple videos that the GSAP team emphasizes not worrying about units, but I've only encountered problems. For instance, I set the image to have a width of 200px in CSS and specified 100% width as the target value in GSAP. Even before the scroller reaches the trigger start, it applies some inline style causing the image to widen. However, if I use window.innerWidth, there's no change in size. But the main issue still remains with responsiveness. If I give GSAP 50%, why doesn't it render that in the inline style? Why does it convert it to pixels? This makes it completely unresponsive, and I have no idea how to make it adjust to fullscreen when the size changes. Is there a way to clear the GSAP inline style when a screen size is changed (I don't mean breakpoints, I know about gsap.matchMedia(), but it is not a solution for this) and recalculate it? I'd be incredibly grateful for any help. Thanks in advance! Here is my css: .img-container { position: relative; } .hero-cont { position: relative; display: flex; flex-direction: column; min-height: 100svh; overflow-x: hidden !important; } .hero__content { width: 100%; margin-top: 28vh; .hero__title { font-size: clamp(3rem, 6vw + 0.1rem, 5rem); font-weight: 600; line-height: 1.1; letter-spacing: 1px; margin-bottom: 30px; } .hero__text { font-size: clamp(1rem, 6vw + 0.1rem, 1.5rem); max-width: 35ch; } } .hero__bg { position: absolute; z-index: -1; object-fit: cover; } .hero-img__container { position: absolute; height: 60%; width: 200px; // left: 25%; position: absolute; left: 0; right: 0; top: 25%; margin-inline: auto; rotate: 15deg; // transform: translate(-30%, -50%); z-index: -1; border: 1px solid white; border-width: 15px 15px 40px 15px; box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.2); // transition: all 400ms ease-in; .hero-photo__img { object-fit: cover; width: 100%; height: 100%; } } Here is my component: 'use client'; import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import Hero from '@/components/Hero'; import gsap from 'gsap'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; import Image from 'next/image'; const heroImg = '/assets/images/hero/hero2.webp'; const heroPhotoImg = '/assets/images/hero/hero-photo.webp'; export default function Home() { const heroComp = useRef(null); useLayoutEffect(() => { gsap.registerPlugin(ScrollTrigger); let ctx = gsap.context(() => { let tl = gsap.timeline({ scrollTrigger: { trigger: heroComp.current, //trigger start: '+=10 top', end: 'bottom top', toggleActions: 'play', scrub: true, markers: true, pin: true, }, }); tl.to('.hero-img__container', { left: 0, top: 0, borderWidth: 0, transform: 'rotate(0)', height: '100%', width: window.innerWidth, }); tl.to('.hero__content', { opacity: 0, }); }, heroComp); return () => { ctx.revert(); }; }, []); return ( <> {/* <Hero /> */} <section ref={heroComp} className='hero-cont cont full-size'> <Image src={heroImg} alt='hero' className='hero__bg' fill /> <div className='hero__content'> <h1 className='hero__title'> Beautiful Moment <br /> is Everything </h1> <p className='hero__text'>Lorem ipsum dolor sit amet consectetur adipisicing elit. Aut, tempora?</p> </div> <div className='hero-img__container'> <Image fill src={heroPhotoImg} alt='hero' className='hero-photo__img' /> </div> </section> </> ); } Link to comment Share on other sites More sharing options...
GSAP Helper Posted December 5, 2023 Share Posted December 5, 2023 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 Stackblitz that demonstrates 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. 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 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: React (please read this article!) Next Svelte Sveltekit Vue Nuxt 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 More sharing options...
Ganbatte Posted December 5, 2023 Author Share Posted December 5, 2023 Here is the showcase of my problem, on a clean new project: https://gsaptest2.netlify.app/ Please try to resize the window, after loaded the images. style.scss *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box !important; text-decoration: none; list-style: none; // outline: 1px solid rgb(255, 0, 0); } .img-container { position: relative; } .hero-cont { position: relative; display: flex; flex-direction: column; min-height: 100svh; } .hero__content { width: 100%; margin-top: 28vh; .hero__title { font-size: clamp(3rem, 6vw + 0.1rem, 5rem); font-weight: 600; line-height: 1.1; letter-spacing: 1px; margin-bottom: 30px; } .hero__text { font-size: clamp(1rem, 6vw + 0.1rem, 1.5rem); max-width: 35ch; } } .hero__bg { position: absolute; z-index: -1; object-fit: cover; } .hero-img__container { position: absolute; height: 60%; width: 400px; // left: 25%; position: absolute; left: 0; right: 0; top: 25%; margin-inline: auto; rotate: 15deg; // transform: translate(-30%, -50%); z-index: -1; border: 1px solid white; border-width: 15px 15px 40px 15px; box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.2); // transition: all 400ms ease-in; .hero-photo__img { object-fit: cover; width: 100%; height: 100%; } } page.tsx: 'use client'; import { useLayoutEffect, useRef } from 'react'; import gsap from 'gsap'; import Image from 'next/image'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; const heroImg = '/assets/images/hero/hero2.webp'; const heroPhotoImg = '/assets/images/hero/hero-photo.webp'; export default function Home() { const heroComp = useRef(null); useLayoutEffect(() => { gsap.registerPlugin(ScrollTrigger); let ctx = gsap.context(() => { let tl = gsap.timeline({ scrollTrigger: { trigger: heroComp.current, //trigger start: '+=10 top', end: 'bottom top', toggleActions: 'play', scrub: true, markers: true, pin: true, }, }); tl.to('.hero-img__container', { left: 0, top: 0, borderWidth: 0, transform: 'rotate(0)', height: '100%', width: window.innerWidth, }); tl.to('.hero__content', { opacity: 0, }); }, heroComp); return () => { ctx.revert(); }; }, []); return ( <main > <section ref={heroComp} className='hero-cont cont full-size'> <Image src={heroImg} alt='hero' className='hero__bg' fill /> <div className='hero__content'> <h1 className='hero__title'> Beautiful Moment <br /> is Everything </h1> <p className='hero__text'>Lorem ipsum dolor sit amet consectetur adipisicing elit. Aut, tempora?</p> </div> <div className='hero-img__container'> <Image fill src={heroPhotoImg} alt='hero' className='hero-photo__img' /> </div> </section> </main> ); } Link to comment Share on other sites More sharing options...
mvaneijgen Posted December 5, 2023 Share Posted December 5, 2023 Hi @Ganbatte welcome to the forum! We can't debug a 'live' website, there is just no way to modify the code. That is why we ask for a minimal demo (please read our forum guidelines) in a Codepen or if your issue is specific to a framework (it usually isn't) you could use one of our many Stackblitz starter templates! If you could provide that, we'll be happy to take a look for you! Link to comment Share on other sites More sharing options...
Ganbatte Posted December 5, 2023 Author Share Posted December 5, 2023 Sorry, Of course, I understand. I managed to put together a version of it here in the meantime. Thank you in advance for the help! Link: https://stackblitz.com/edit/nextjs-auvnxa?file=pages%2Findex.js Link to comment Share on other sites More sharing options...
Rodrigo Posted December 5, 2023 Share Posted December 5, 2023 Hi, I'm not 100% I follow what exactly you're trying to do. Here is my best guess: See the Pen rNPopvM by GreenSock (@GreenSock) on CodePen Hopefully this helps. If you're not looking for this, please be super specific as to what you're trying to achieve (include a live example that does what you're aiming for) so we can get a clear idea. Happy Tweening! Link to comment Share on other sites More sharing options...
Ganbatte Posted December 5, 2023 Author Share Posted December 5, 2023 Hello Rodrigo! First of all, thank you for your help, but unfortunately, that's not what I had in mind. I've created the editable version which you can find at my link. My problem is that when I resize the window, the image doesn't adjust accordingly. Also, I don't understand why I can't specify CSS values like percentages or viewport width (vw) through GSAP. It seems to convert them directly into pixels, which I find puzzling. I'm mean for this: https://stackblitz.com/edit/nextjs-auvnxa?file=pages%2Findex.js Link to comment Share on other sites More sharing options...
Ganbatte Posted December 5, 2023 Author Share Posted December 5, 2023 While testing on StackBlitz, I managed to figure out a few things. The reason it wasn't responsive was that I used window.innerWidth. I did this because GSAP was overriding the size set in CSS upon loading, which I'm still unsure why it's doing. These lines caused the issue, although theoretically, they shouldn't affect it. When I commented out GSAP, there were no problems. For instance, I set a width of 20% in CSS, but GSAP changed it to 25% even before the animation. Why this happens is unclear. Additionally, it slightly alters the value even without this code. For instance, I set it to 20%, but GSAP overrides it inline to 19.9858%. I'm perplexed as to why it does this. Here's the code (all of this is available on the StackBlitz interface): @mixin containerChild() { > div:not(.img-container) { max-width: 1200px; margin-inline: auto; padding-inline: 10px; } } .cont { @include containerChild(); min-height: 100svh; } Link to comment Share on other sites More sharing options...
Ganbatte Posted December 5, 2023 Author Share Posted December 5, 2023 I would be very grateful if you could help me understand better how exactly GSAP works. Link to comment Share on other sites More sharing options...
Rodrigo Posted December 5, 2023 Share Posted December 5, 2023 4 hours ago, Ganbatte said: My problem is that when I resize the window, the image doesn't adjust accordingly. Sorry, I'm still having issues getting this through my thick skull 😞. What I'm seeing in that demo is exactly what should happen. Also the values being interpolated by GSAP in the image container are in percentage for the width and height as far as I can see. Maybe I'm missing something here. How the image should adjust? I would recommend you to use scale values (if possible) for the image in order to get better performance. Animating Height, Width, Top/Left/Ritgh/Bottom, margins, paddings, etc. can be very expensive because it'll trigger a repaint on every value change, so is better to use scale. That's why in my demo I set the image to be in it's final value (covering the entire container) and then I scale it down in order to animate it to scale 1. As for the values being a bit odd, sometimes complex calculations can result in a decimal position, very far away, to get an unexpected value. Is that presenting a problem in your setup currently? Happy Tweening! Link to comment Share on other sites More sharing options...
Ganbatte Posted December 7, 2023 Author Share Posted December 7, 2023 Thanks for the tip and the information, Rodrigo! Unfortunately, I can't use scale or valut in this case because I also modify the cropping of the image. However, I'm satisfied with the solution. But I have a problem: when I put it in a separate component and create the timeline in the main component, then pass it over, I can't properly use the scroll trigger. How can I use scroll triggers for multiple animations on one page? What am I doing wrong? The scroll trigger only work with my first animations. I really want to get the hang of using GSAP, it seems like a great tool, but I just haven't quite gotten the hang of it yet. I would be extremely grateful for any help. Thank you so much in advance. here is the demo:https://stackblitz.com/edit/nextjs-wr4ktj?file=pages%2Findex.js,pages%2FHero.js,pages%2F_app.js Link to comment Share on other sites More sharing options...
Rodrigo Posted December 8, 2023 Share Posted December 8, 2023 Hi, I only see one issue in your code, you are creating a to() instance with a ScrollTrigger config inside of a timeline. That's a logic problem as explained here: https://gsap.com/resources/st-mistakes#nesting-scrolltriggers-inside-multiple-timeline-tweens On that note, I don't really see the need to create a reference to that particular timeline in the parent component TBH. The animation is controlled by ScrollTrigger and has scrub in it. Nothing in the parent component should have any control over that timeline, since is controlled by the scroll position using ScrollTrigger. My advice would be to create the timeline for the Hero component inside that component and move the ScrollTrigger configuration to the config object of the timeline. // Hero.js export default function Hero() { useLayoutEffect(() => { const ctx = gsap.context(() => { gsap.registerPlugin(ScrollTrigger); const timeline = gsap.timeline({ scrollTrigger: { trigger: container.current, start: '+=10 top', end: 'bottom top', toggleActions: 'play', markers: true, scrub: true, pin: true, }, }); timeline.to('.hero-img__container', { left: 0, top: 0, borderWidth: 0, transform: 'rotate(0)', height: '100%', width: '100%', duration: 3, }); timeline.to('.hero__content', { opacity: 0, transform: 'translateY(50px)', }); }, container); return () => ctx.revert(); }, []); return ( // JSX Here ); }; Hopefully this helps. Happy Tweening! Link to comment Share on other sites More sharing options...
Ganbatte Posted December 9, 2023 Author Share Posted December 9, 2023 Thanks again, Rodrigo. I've tried this solution myself, but it results in having 2 scroll triggers on the homepage. If I create more sections on the homepage that I want to animate, there could potentially be 5-10 scroll triggers. Isn't the goal of the scroll trigger to have just one on the homepage in certain cases, and then modify the trigger itself along with the pinning element? Is this possible, or am I thinking about it in a completely wrong way? How do larger websites usually implement this? Unfortunately, I can't find any tutorials online that cover this scenario, they all demonstrate a single case. I've improved the demo and added an extra section to make my point clearer. Thank you very much in advance for your help! link is the same: https://stackblitz.com/edit/nextjs-hvbhri?file=pages%2Findex.js,pages%2FHero.js,pages%2FBeforeAfterScroll.js,styles%2Fglobals.scss Link to comment Share on other sites More sharing options...
Rodrigo Posted December 9, 2023 Share Posted December 9, 2023 I'm on my phone now so I can't take a look at the demo. But you can have as many scrolltrigger instances as you want as long as you create them in the order they apper in the screen. Just create them inside each component and put those components in the right order. This page of a Next project was made with ScrollTrigger a few years ago using that approach, and as you can see nothing is breaking https://wiredave.com/artificial-intelligence Happy Tweening! Link to comment Share on other sites More sharing options...
Ganbatte Posted December 10, 2023 Author Share Posted December 10, 2023 Thank you again! I will try. 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