FlySandwich Posted May 12, 2025 Posted May 12, 2025 Hi GSAP Team! I'm trying to use the scope feature to scope selector and make components more reusable that they don't trigger each others' animation if there are multiple of them in the scene. In the doc, the usage example is pretty simple, and I am trying to put more things into useGSAP, like creating a timeline (in the demo), or ScrollTrigger.Create() (not in the demo) with normal classname selector. However, in the demo, the second page's boxes will fade out with the first page's boxes fading out, even with a ref selector. I can solve this by a dedicated select using the gsap utils like this q = gsap.utils.selector(containerRef) tl.to(q(".className"), { ... }) and it will help finding the correct child element. So, my question is, is the scope not working if I use timeline or create a scrollTrigger pinning the element? I can only use simple gsap.to/from/fromto etc? a scrollTirgger example might be: useGSAP(() => { // pin ScrollTrigger.create({ trigger: ".section", start: "top top", end: "bottom top", pin: ".section-content", pinSpacing: false, scrub: 0.00001, markers: true, }); }, {scope: sectionRef}) this is the demo, it contains the behavior I am talking about. https://stackblitz.com/edit/gsap-react-basic-f48716-c6w4dfnr?file=src%2FApp.js Thank you GSAP Team for helping! See the Pen App.js by edit (@edit) on CodePen.
Rodrigo Posted May 12, 2025 Posted May 12, 2025 Hi, The problem is that the ref you're passing as scope is the same selector you're using, so basically GSAP is doing something like this: container.ref.querySelectorAll(".boxes-container"); But the element with the boxes-container class is the same, so there is no child nodes with that class so that selector returns an empty NodeList array-like object, so GSAP looks up and find all the elements with that class you have in your JSX. This seems to solve it: return ( <main ref={container}> <section className="boxes-container"> <div className="box gradient-blue">Box 1</div> <div className="box gradient-blue">Box 2</div> <div className="box gradient-blue">Box 3</div> </section> </main> ); Like that GSAP will find a single child element with the selector you're using on each component instance that is rendered. Here is a fork of your demo: https://stackblitz.com/edit/gsap-react-basic-f48716-aojcekoh?file=src%2FApp.js,src%2Findex.js Hopefully this clear things up Happy Tweening!
FlySandwich Posted May 13, 2025 Author Posted May 13, 2025 That solved the specific problem, but in this code, even I have a wrapper and using that ref, the animations of all this components will run at the same time. It works after I use the selector which is the 'q' method in the code. If you want a sandbox I can try to make one but I think this might be enough to see some problems. An example will be, by removing q() in tl_section_transIn will lead all panels transition in at the same time when scrolling. import { useGSAP } from "@gsap/react"; import React from "react"; import { ScrollTrigger } from "gsap/all"; import gsap from "gsap"; gsap.registerPlugin(ScrollTrigger, useGSAP); /** * @param sectionName - must be unique for each section. it is ued to create the class name for the section */ const PanelSection = ({ sectionName, children, }: { sectionName: string; children?: React.ReactNode; }) => { const containerRef = React.useRef(null); const q = gsap.utils.selector(containerRef); useGSAP( () => { const tl_section_transIn = gsap.timeline({ scrollTrigger: { trigger: q(`.section-spacer1`), start: "top top", end: "bottom top", scrub: 0.00001, }, }); const tl_section_fadeout = gsap.timeline({ scrollTrigger: { trigger: q(`.section-spacer3`), start: "top top", end: "bottom top", scrub: 0.00001, onEnter: () => { gsap.to(`.section-content`, { zIndex: 0 }); }, onLeaveBack: () => { gsap.to(`.section-content`, { zIndex: 20 }); }, }, }); // pin ScrollTrigger.create({ trigger: containerRef.current, start: "top top", endTrigger: q(`.section-spacer3`), end: `bottom top`, pin: q(`.section-content-container`), pinSpacing: false, scrub: 0.00001, anticipatePin: 1, // markers: true, }); tl_section_transIn.to(q(`.section-content`), { opacity: 1, left: 0, }); tl_section_fadeout.to(q(`.section-content`), { opacity: 0, scale: 0.5, ease: "power2.inOut", rotateY: 60, }); }, { scope: containerRef } // for some reason, it is not working even I added this, so I use q() ); return ( <React.Fragment> <div ref={containerRef}> <div className="section-content-container w-full perspective-normal"> <div className="section-content h-screen absolute w-full flex items-center justify-center py-20 px-40 opacity-0 z-20 left-1/1 origin-bottom-left" > <div className="section-panel h-full w-full max-w-[1200px] relative glassmorphism-card overflow-hidden"> {children} </div> </div> </div> <div className="section-spacer1 h-screen w-full text-5xl text-white">1</div> <div className="section-spacer2 h-screen w-full text-5xl text-white">2</div> <div className="section-spacer3 h-screen w-full text-5xl text-white absolute"> 3 </div> </div> </React.Fragment> ); }; export default PanelSection;
Rodrigo Posted May 13, 2025 Posted May 13, 2025 Hi, That shouldn't be the case, the selector should work as expected with the provided scope and without the selector utility method. Also you shouldn't do this: const containerRef = React.useRef(null); const q = gsap.utils.selector(containerRef); By the time the selector utility method runs the containerRef is null so it doesn't return anything. The selector (which again is not needed) should be inside the useGSAP hook so the ref is actually rendered and not null. If you keep having issues, please present a small stackblitz demo that clearly illustrates the problem. Happy Tweening!
GreenSock Posted May 13, 2025 Posted May 13, 2025 16 minutes ago, Rodrigo said: Also you shouldn't do this: const containerRef = React.useRef(null); const q = gsap.utils.selector(containerRef); By the time the selector utility method runs the containerRef is null so it doesn't return anything. The selector (which again is not needed) should be inside the useGSAP hook so the ref is actually rendered and not null. I'm not much of a React guy but I think that's okay because it doesn't actually need the containerRef.current right away - it'll only check that when you actually feed the selector something. gsap.utils.selector(...) returns a function, so when that function gets invoked, THAT is when it'll check containerRef.current. It'll definitely be a HUGE help if you can provide a minimal demo (like a Stackblitz) that illustrates the issue so we can take a peek and see it in the proper context. I don't think anyone else has reported any issue like this with the selector functionality, so I wonder if there's just a misunderstanding somewhere(?)
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