Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...

Search the Community

Showing results for tags 'react'.

  • Search By Tags

    Type tags separated by commas.
  • Search By Author

Content Type


Forums

  • GreenSock Forums
    • GSAP
    • Banner Animation
    • Jobs & Freelance
  • Flash / ActionScript Archive
    • GSAP (Flash)
    • Loading (Flash)
    • TransformManager (Flash)

Product Groups

  • Club GreenSock
  • TransformManager
  • Supercharge

Categories

There are no results to display.


Find results in...

Find results that contain...


Date Created

  • Start

    End


Last Updated

  • Start

    End


Filter by number of...

Joined

  • Start

    End


Group


Personal Website


Twitter


CodePen


Company Website


Location


Interests

  1. Hi just joined to club GreenSock and I'm trying to make animation with DrawSVGPlugin ,following steps of codepen example, and Nextjs. The SVG renders for 1 second then it disappears. I attach a image with the code I have
  2. Hi, I am trying to animated section with scroll created as components in React. You can find a similar effect to what I am trying to achieve here (the text on the right or the images inside the phone). In my animation I would like the headingOne to appear from the bottom of the screen and fade away towards the top of the screen, followed by the headingTow that should come from the right or left followed by the cards while the section is pined and only after the animation finished the normal scrolling would begin again. Doing this I have encountered some difficulties. One of which beaning an error that says ";" expected while using the TweenLite and I don't really see why... Also I am not use that I used gsap correctly. For the headingOne I would like to display it none, but only after the fade away animation finishes so the headingOne would come into the middle of the screen. I would greatly appreciate is you could take a look at the code pen and point out my mistakes. Thank you!
  3. React is a hugely popular library choice, and as evidenced by many of the sites in our showcase - React and GSAP can be a powerful combination. However, utilizing GSAP in React requires a different way of thinking than a vanilla JS project. We've written this guide to help you get started using GSAP within a React project. This is not a tutorial, so feel free to dip in and out as you learn. Think of it as a collection of recommended techniques and best practices to use in your projects. Why GSAP? Animating with GSAP gives you unprecedented levels of control and flexibility. You can reach for GSAP to animate everything — from simple DOM transitions to SVG, three.js, canvas or WebGL — your imagination is the limit. More importantly, you can rely on us. We obsess about performance, optimizations and browser compatibility so that you can focus on the fun stuff. We've actively maintained and refined our tools for over a decade and there are no plans to stop. Lastly, if you ever get stuck, our friendly forum community is there to help. Going forward we will assume a basic understanding of GSAP and React. If you're just getting going with React, this tutorial from the React team is a great place to start. Need a GSAP refresher? Take a break and read about tweens and timelines. We’ll be here when you get back. Feeling confident? Skip straight to part 2 - GSAP + React, advanced animation techniques. Quick Links Getting set up Targeting a DOM element for animation Creating our first animation Targeting descendant elements Creating a timeline Controlling when React runs our animation with useEffect Reacting to changes in state Animating on interaction Avoiding flash of unstyled content (FOUC) Cleaning up Online Playgrounds Get started quickly by forking one of these starter templates: CodePen CodeSandbox CodeSandbox + Bonus Plugins Create a new React App If you prefer to work locally, Create React App provides a comfortable setup for experimenting with React and GSAP. To create a project, run: npx create-react-app gsap-app cd gsap-app npm start Once the project is set up we can install GSAP through npm, npm i gsap npm start then import it into our app. import React from "react"; import { gsap } from "gsap"; export default function App() { return ( <div className="app"> <div className="box">Hello</div> </div> ); } More detailed information about getting started with React Additional GSAP installation documentation Targeting elements In order to animate using GSAP we need access to the element in the DOM. Refs provide a way for us to interact with and store references to DOM nodes in a React component. const boxRef = useRef(); return <div className="box" ref={boxRef}>Hello</div>; Read more about refs in the React docs Creating our first animation GSAP updates inline style properties, so it’s important to make sure the DOM has been rendered before trying to animate anything. If we ask GSAP to animate an element that hasn’t been rendered, we’ll get this warning in the console. GSAP target not found. In order to avoid targeting a null element, we can use the useEffect hook. This hook tells React that our component needs to do something after rendering. function App() { // store a reference to the box div const boxRef = useRef(); // wait until DOM has been rendered useEffect(() => { gsap.to(boxRef.current, { rotation: "+=360" }); }); // DOM to render return <div className="box" ref={boxRef}>Hello</div>; } In this example, React will first render the box element to the DOM, then GSAP will rotate the box 360deg. See the Pen React & GSAP Starter Template by GreenSock (@GreenSock) on CodePen. Targeting descendant elements gsap.utils.selector() Creating a ref for each and every element we want to animate can add a lot of noise to our code. We can avoid this by making use of GSAP’s selector utility to easily select descendant elements. const el = useRef(); const q = gsap.utils.selector(el); useEffect(() => { // Target ALL descendants with the class of .box gsap.to(q(".box"), { x: 100 }); }, []); See the Pen gsap.utils.selector() by GreenSock (@GreenSock) on CodePen. Forwarding refs gsap.utils.selector() will target all descendants in the component tree. Within a component based system, you may need more granular control over the elements you're targeting. You can use ref forwarding to get access to specific nested elements. See the Pen Forwarding refs by GreenSock (@GreenSock) on CodePen. Creating and controlling timelines Up until now we've just used refs to store references to DOM elements, but they're not just for elements. Refs exists outside of the render loop - so they can be used to store any value that you would like to persist for the life of a component. If you're coming from class based components, this should be familiar to you as it’s essentially the same as using ‘this’. In order to avoid creating a new timeline on every render, it's important to create the timeline inside an effect and store it in a ref. function App() { const el = useRef(); const q = gsap.utils.selector(el); const tl = useRef(); useEffect(() => { tl.current = gsap.timeline() .to(q(".box"), { rotate: 360 }) .to(q(".circle"), { x: 100 }); }, []); return ( <div className="app" ref={el}> <Box>Box</Box> <Circle>Circle</Circle> </div> ); } This will also allow us to access the timeline in a different effect and toggle the timeline direction. See the Pen React Tutorial 2f by GreenSock (@GreenSock) on CodePen. Controlling when React runs our animation. By default useEffect runs both after the first render and after every update. So every time our component’s state changes, it will cause a re-render, which will run our effect again. We can control when useEffect should run by passing in an array of dependencies. To only run once after the first render, we pass in an empty array. // only runs after first render useEffect(() => { gsap.to(q(".box-1"), { rotation: "+=360" }); }, []); // runs after first render and every time `someProp` changes useEffect(() => { gsap.to(q(".box-2"), { rotation: "+=360" }); }, [someProp]); // runs after every render useEffect(() => { gsap.to(q(".box-3"), { rotation: "+=360" }); }); See the Pen React Tutorial 1b by GreenSock (@GreenSock) on CodePen. Reacting to changes in state Now that we know how to control when an effect fires, we can use this pattern to react to changes in our component. This is especially useful when passing down props. function Box({ children, endX}) { const boxRef = useRef(); // run when `endX` changes useEffect(() => { gsap.to(boxRef.current, { x: endX }); }, [endX]); return ( <div className="box" ref={boxRef}>{children}</div> ); } See the Pen React Tutorial 1c by GreenSock (@GreenSock) on CodePen. Animating on interaction Interaction is one of the most exciting things about animating on the web! In order to hook into user interactions like hover, we can use callbacks. const onEnter = ({ currentTarget }) => { gsap.to(currentTarget, { backgroundColor: "#e77614" }); }; const onLeave = ({ currentTarget }) => { gsap.to(currentTarget, { backgroundColor: "#28a92b" }); }; return ( <div className="box" onMouseEnter={onEnter} onMouseLeave={onLeave}> Hover Me </div> ); See the Pen React Tutorial 1d by GreenSock (@GreenSock) on CodePen. Avoiding flash of unstyled content (FOUC) As useEffect fires after the DOM has been painted, when fading in elements you may notice an undesired flash of unstyled content. In order to avoid the flash, we can replace useEffect with useLayoutEffect. useLayoutEffect functions exactly the same as useEffect, but runs before the DOM has been painted. See the Pen Avoiding FOUC with useLayoutEffect() by GreenSock (@GreenSock) on CodePen. useLayoutEffect is especially useful when you need to make DOM measurements, so we highly recommend it when using our ScrollTrigger and FLIP plugins. More information about useEffect vs useLayoutEffect. Cleaning Up It’s a good idea to return a cleanup function in your effects to kill off any running animations and anything else that could cause a memory leak, like an event listener. This is particularly important if an animation runs for a really long time, makes use of ScrollTrigger, or changes the state in a component. useEffect(() => { const animation1 = gsap.to(".box1", { rotation: "+=360" }); const animation2 = gsap.to(".box2", { scrollTrigger: { ... } }); const onMove = () => { ... }; window.addEventListener("pointermove", onMove); // cleanup function will be called when component is removed return () => { animation1.kill(); animation2.scrollTrigger.kill(); window.removeEventListener("pointermove", onMove); }; }, []); We hope this article was helpful - If you have any feedback please leave us a comment below so we can smooth out the learning curve for future animators! Feeling confident and want to learn more? Check out our follow up article - GSAP + React, advanced animation techniques.
  4. Hello! I'm working with Next.js and have run into a logistical issue with Refs. https://codesandbox.io/s/nested-react-next-gsap-gvksxp ^^ In the sandbox I have my IndexPage mapping out a few items, problem being, almost half of the elements need animation, and this means doing const thing = useRef([]) thing.current = [] const addToRef = (el) => { if (el && !thing.current.includes(el)) { thing.current.push(el); } }; A bunch I have animated the text but targeting the rest of the stuff that needs tweens seems like a ton of work (unless I'm missing something) Is there a recommended approach for animating a bunch of children elements that are being mapped out? I'd love to use a ref on the container div and target down to any level with .children[i] or .querySelectorAll but this hasn't quiet panned out in my experiments. I appreciate the advice, please let me know if my demo needs adjusting or if I can clarify anything! Nye
  5. Hello! I want to move the purple block along the path I've draw in the SVG. This is one section of my website which uses gsap all over the page. Rest of the page can be easily selected using the ref but when I use MotionPath, It throws Invalid scope and Target not found error. Here's the codesandbox: https://codesandbox.io/s/gsap-motionpath-react-bkscsf?file=/src/App.js Thank You!
  6. Hello, I'm working on a React component for parallax image effect. The component: import React, { FC, useLayoutEffect, useRef } from 'react'; import { gsap } from 'gsap'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; gsap.registerPlugin(ScrollTrigger); interface ParallaxImageProps { src: string; alt?: string; } export const ParallaxImage: FC<ParallaxImageProps> = ({ src, alt }) => { const parallaxImage = useRef(null); const parallaxImageInner = useRef(null); useLayoutEffect(() => { const tl = gsap.timeline({ scrollTrigger: { trigger: parallaxImage.current, scroller: '.nice-scroll-container', scrub: true, pin: false, }, }); tl.from(parallaxImageInner.current, { yPercent: -10, ease: 'none', }).to(parallaxImageInner.current, { yPercent: 10, ease: 'none', }); }, []); return ( <figure className="parallax-image" ref={parallaxImage}> <img src={src} alt={alt} className="parallax-image__inner" ref={parallaxImageInner} onLoad={() => ScrollTrigger.refresh()} /> </figure> ); }; I had tested onEnterand onLeavemethods and those executed in right time but animating image from yPercent: -10 to yPercent: 10 have not been working properly and have been triggering when the image comes in middle of the viewport. Any solution? Live Demo
  7. Hey, I have kinda a big Issue - i've implemented a Text Rotation into a React Application. Locally everything works completely fine. After building it and deploying it on a Server (as a static version) it crashes and is not rotating as it was rotating locally. When it arrives at the last step it scrolls all items down until the first. it's not rotating from the beginning. Check for the Comment " // Start Rotation Animation". GSAP v3.8.0 is installed. Check the WeTransfer link for demo videos of local and staging env. https://we.tl/t-9MCJFOHefo Check the Demo Instance: https://gsap.elbcouture.com/ (maybe you have to reload it a few times, if the issue didn't happend) /* global fullpage_api */ import React, { createRef, useEffect, useRef } from 'react'; import gsap, { Power0 } from 'gsap'; import styled from 'styled-components'; import Header from 'components/Header'; import Stage from 'components/Stage'; import Headline from 'components/Headline'; import ScrollToExplore from 'components/ScrollToExplore'; import xsmallVideo from '../../assets/video/intro/414x896.mp4'; import smallVideo from '../../assets/video/intro/768x1024.mp4'; import mediumVideo from '../../assets/video/intro/1024x768.mp4'; import largeVideo from '../../assets/video/intro/1920x1080.mp4'; const StyledIntro = styled(Stage)` padding: 0; &::before { z-index: 1; pointer-events: none; } `; const StyledHeader = styled(Header)` display: block; @media screen and (min-width: 1024px) { display: none; } `; const StyledContent = styled.div` position: absolute; z-index: 1; margin-top: 71px; padding-left: 15px; @media screen and (min-width: 414px) { margin-top: 115px; } @media screen and (min-width: 768px) { margin-top: 70px; } @media screen and (min-width: 1024px) { right: 0; display: flex; flex-direction: column; justify-content: center; margin-top: 0; width: 50%; height: 100%; } @media screen and (min-width: 1440px) { right: 0; display: block; margin-top: 228px; width: 50%; height: 100%; } @media screen and (min-width: 1920px) { width: 46%; } @media screen and (min-width: 2560px) { margin-top: 15%; } `; const StyledMaskedText = styled(Headline)` font-size: 54px; line-height: 65px; ${({ backgroundImage }) => backgroundImage?.small ? `background: url(${backgroundImage.small}) no-repeat;` : ''} background-size: cover; background-position: center; -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; @media screen and (min-width: 414px) { font-size: 64px; line-height: 77px; } @media screen and (min-width: 768px) { font-size: 104px; line-height: 125px; } @media screen and (min-width: 1024px) { font-size: 74px; line-height: 88px; } @media screen and (min-width: 1440px) { font-size: 98px; line-height: 129px; } @media screen and (min-width: 1920px) { font-size: 124px; line-height: 124px; } @media screen and (min-width: 2560px) { font-size: 130px; line-height: 120px; } `; const StyledHeadline = styled(Headline)` height: 52px; font-size: 44px; line-height: 52px; color: #00917e; overflow: hidden; white-space: nowrap; @media screen and (min-width: 414px) { height: 62px; font-size: 51px; line-height: 62px; } @media screen and (min-width: 768px) { height: 100px; font-size: 83px; line-height: 100px; } @media screen and (min-width: 1024px) { height: 71px; font-size: 59px; line-height: 71px; } @media screen and (min-width: 1440px) { height: 103px; font-size: 86px; line-height: 103px; } @media screen and (min-width: 1920px) { height: 132px; font-size: 105px; line-height: 132px; } @media screen and (min-width: 2560px) { height: 160px; font-size: 140px; line-height: 160px; } `; const StyledList = styled.div` list-style-type: none; padding: 0; margin: 0; `; const StyledVideo = styled.video` position: absolute; left: 0; right: 0; bottom: 0; width: 100%; @media screen and (min-width: 1024px) { position: static; display: block; height: 100vh; } `; const StyledHeadlineItem = styled.span` display: block; `; const Intro = ({ backgroundImage, switchingHeadline, active, lastActive, text, scrollTo }) => { const stageRef = useRef(null); const videoRef = useRef(null); const overlineRef = useRef(null); const headlineRef = useRef(null); const sublineRef = useRef(null); const listRef = useRef(null); const listItemRef = useRef([]); const scrollToExploreRef = useRef(); useEffect(() => { switchingHeadline.push(switchingHeadline[0]); if (listItemRef.current.length !== switchingHeadline.length) { // add or remove refs listItemRef.current = Array(switchingHeadline.length) .fill() .map((_, i) => listItemRef.current[i] || createRef()); } }, [switchingHeadline]) const getVideoSrc = (width) => { if (width >= 1920) return largeVideo; if (width >= 1024) return mediumVideo; if (width >= 768) return smallVideo; return xsmallVideo; }; // Start Rotation Animation const vSlide = gsap.timeline({ repeat: -1, paused: true, }); useEffect(() => { console.log('el', listItemRef.current) listItemRef.current.forEach((_slide, index) => { const label = `slide${index}`; const lineHeight = headlineRef.current.clientHeight; if (active) { if (index === 0) { vSlide.to( listRef?.current, { delay: 0, duration: 0.4, y: index * -1 * lineHeight, ease: Power0.ease, }, label ); } if (index > 0) { vSlide.to( listRef?.current, { delay: 3, duration: 0.4, y: index * -1 * lineHeight, ease: Power0.ease, }, label ); } vSlide.play(); } vSlide.add(label); }); }, [vSlide, active]); // End Rotation Animation const [isVideoLoaded, setIsVideoLoaded] = React.useState(false); const src = getVideoSrc(window.innerWidth); const onLoadedData = () => { setIsVideoLoaded(true); }; useEffect(() => { if (active) { videoRef.current.currentTime = 0; gsap.set([overlineRef.current, headlineRef.current, sublineRef.current], { y: 0, opacity: 1, }); gsap.set(scrollToExploreRef.current, { opacity: 1, }); } if (lastActive) { videoRef.current.play(); gsap.to(overlineRef.current, { opacity: 0, delay: 0, duration: 0.25, ease: Power0.in, }); gsap.to(headlineRef.current, { opacity: 0, delay: 0, duration: 0.25, ease: Power0.in, }); gsap.to(sublineRef.current, { opacity: 0, delay: 0, duration: 0.25, ease: Power0.in, }); gsap.to(scrollToExploreRef.current, { opacity: 0, delay: 0, duration: 0.25, ease: Power0.in, }); vSlide.pause(); } }, [active, lastActive, vSlide]); return ( <StyledIntro className="fp-noscroll" ref={stageRef}> <StyledHeader /> <StyledContent> <StyledMaskedText ref={overlineRef} backgroundImage={backgroundImage}> {text?.[0]} </StyledMaskedText> {switchingHeadline && ( <StyledHeadline ref={headlineRef}> <StyledList ref={listRef}> {switchingHeadline?.map((item, index) => ( // eslint-disable-next-line react/no-array-index-key <StyledHeadlineItem key={`${item}-${index}`} ref={listItemRef.current[index]}> {item} </StyledHeadlineItem> ))} </StyledList> </StyledHeadline> )} <StyledMaskedText ref={sublineRef} backgroundImage={backgroundImage}> {text?.[1]} </StyledMaskedText> </StyledContent> <StyledVideo ref={videoRef} playsInline muted src={src} onLoadedData={onLoadedData} style={{ opacity: isVideoLoaded ? 1 : 0 }} /> <ScrollToExplore className="scroll-to-explore" isLight label={scrollTo} dataLabel={scrollTo} ref={scrollToExploreRef} onClick={() => ( fullpage_api.moveSectionDown() )}/> </StyledIntro> ); }; export default Intro;
  8. Hi, Is there any way to target another element for adding class while the trigger elements enters and exits viewport. Like a target prop or something. Or just add animation to another set of images. In the below code I am trying to add "active" class to the target element ".anim-phone-image" but i guess there is no such property 😅. Please help me out useEffect(() => { const scrollTexts = gsap.utils.toArray('.anim-text'); scrollTexts.forEach((item, index) => { tl.current = gsap.timeline({ scrollTrigger: { trigger: item, scrub: true, start: 'top 30%', // start when top of trigger target hits 50% point of viewport end: 'bottom 10%', toggleClass: `active-${index}`, markers: true, target: '.anim-phone-image', }, }); }); }, []);
  9. Here's the codesandbox: https://codesandbox.io/s/gsap-transition-snap-react-p421y5 I want the building to smoothly disappear when it hits the trigger point. Instead of animating, building just snaps to opacity: 0.
  10. Hello everyone, This is a reformulation of a topic that I created a few days ago. I am creating a new one here because I think it is more a ScrollTrigger+React-related problem and that it might benefit from being referenced as such. Context There are two consecutive "sections" that are both a 100vw/100vh. Each section is a React component (in the CodeSandbox below, they're called respectively WorkOverview and HomeAbout). They both get pinned one after the other. Problem The second element gets pinned too early, exactly as if the padding of the first section's .pin-spacer wasn't taken into account. The weirdest thing is that it doesn't happen all the time (but must of the time). Please note that (1) the ScrollTriggers are created in the order they happen on the page and that (2) it is not caused by any asynchronously-loaded content on what the sections' sizing might rely (images are inside a pre-sized container). Here's a video that illustrate the problem: Here's the CodeSandbox link https://codesandbox.io/s/clever-rhodes-16ic1. Note: if you don't see the problem, refresh the page 1 or 2 times. Thanks in advance for you precious help!
  11. // Parent Component export default function ParentComponent() { const [title, setTitle] = useState('values'); const changeContent = (title) => { setTitle(title); } const bgRef = useRef(null); const mainRef = useRef(null); const bgTransitionRef = useRef(null); useEffect(()=>{ const tl = gsap.timeline(); tl.fromTo(bgTransitionRef.current, {y: 0, skewY: 0}, {y: "-120vh", skewY: 4, duration: 1.2, ease: "power4.inOut"}) .fromTo(bgRef.current, {filter: "brightness(3) grayscale(150%)"}, {filter: "brightness(1) grayscale(0%)", duration: 1.2}) .fromTo(mainRef.current, {y: 150, autoAlpha: 0}, {y: 0, autoAlpha: 1, duration: 1.2, ease: "power3.inOut"}, "<0.5") }) return ( <div className="parent-component"> <img className='bg-background' src="/assets/clouds.png" ref={bgRef} /> <div className="bg-transition" ref={bgTransitionRef} ></div> <ChildComponent changeContent={() => changeContent('values')} /> <div/> ) } // Child Component export default function ChildComponent(props) { return ( <div className="child-component" onClick={props.changeContent}> <h4>{props.title}</h4> </div> ) } Alrighty, so what's happening is that whenever I click the ChildComponent, it should run a function called 'changeContent'. It does change the content when I click it. But what it also does is that it restarts all of the GSAP animations. Why is it doing this? And how can I prevent it from happening?
  12. Hello All I'm trying to make and endless loop with two rows going in opposite directions. The first row works fine, but the second row has a bug where there is a gap of unrendered elements after Infatuation. Any ideas on what's going on? Code is Below (Apologies, but I have issues making react Code Pens): https://codesandbox.io/s/test-carousel-forked-s5wtub
  13. This isn't a question. I'm posting this here because it took me about half a day to realize what's going on. Problem: If you use a from tween the animation get's stuck in the from position OR it animates a very small portion of the start of this tween. Solution: Go to your index.js file and remove the <React.StrictMode> wrapper around your rendered Root component. Explanation (My best guess): The tween actually is fully animating. You can check with onStart/onComplete callbacks. Strict mode causes your components to mount twice. Since GSAP has its own state management system you've created two instances of the from tween. These are competing with each other. Apologies if this is written in the documentation somewhere. I didn't see it. Happy Tweening.
  14. Hi everyone, First of all, no need to tell you that the library is just amazing. I just discovered it and was going through the examples. I viewed the vido about the FLIP plugin and I have a question regarding the implementation in React. in Js, it is pretty simple to move element through the dom but i tried to make something similar in React and to be honest, I have no idea on how to move a component from 1 div to another. What I tried is to put the components 2 imes in the code and use a toggle state to show it in one place and remove it in the other but the Flip do not work. I suppose that GSAP consider that they are 2 different components Any guidelines on how to use Flip with React ? In all the samples that I have seen, the elements are moved within a common parent and it works just well For example, how to convert the flip example in this codepen to React? https://codepen.io/GreenSock/pen/ExyzePZ Thanks a lot for your advices. Best regards.
  15. Hello, I'd like to use GSAP ScrollSmoother on top of ScrollTrigger (with some SlitText) but I have animation issue. I'm trying to use GSAP as a reusable component with React like the documentation here (section #reusable-animations) But it's not working well, the 1st animation is running smoothly but the other one are juste popping in the screen. As soon as I remove the ScrollSmoother.create(); the animations are good again. So definively something on ScrollSmoother side. Do you know what cause the issue? Thanks Alex
  16. Hi there, I am using ScrollTrigger a lot, its a fantastic addition! the only issue I've run into so far is cleaning up when using ScrollTrigger.matchMedia() when un-mounting in my React components. I've tried killing all ScrollTrigger instances, and killing timelines individually. simplified setup below: const buildTimeline = () => { // ... setup stuff ScrollTrigger.saveStyles([boxRef.current, mobileRef.current]); ScrollTrigger.matchMedia({ '(min-width: 720px)': () => { if (!boxRef.current) { console.log('boxRef does not exist'); } ScrollTrigger.create({ // config stuff animation: desktopTimeline.current .to( // animations ) }); }, '(max-width: 719px)': () => { if (!mobileRef.current) { console.log('mobileRef does not exist'); } ScrollTrigger.create({ // config stuff animation: mobileTimeline.current .to( // animations ) }); }, }); } useEffect(() => { if (!hasMounted.current) { hasMounted.current = true; buildTimeline(); } return () => { // kill all ScrollTrigger[s] ScrollTrigger.getAll().forEach(t => t.kill()); // try killing individual timelines also mobileTimeline.current.kill(); desktopTimeline.current.kill(); } }, []); This would normally work ok on ScrollTrigger instances generally - or at least it seems to! - but if I'm using matchMedia I'll still get media query change events firing where the component is unmounted (i.e. navigating to a different route). any idea what I'm missing here?
  17. Are you working with React and looking to really advance your GSAP animation skills? You're in the right place. This guide contains advanced techniques and some handy tips from expert animators in our community. This is not a tutorial, so feel free to dip in and out as you learn. Think of it as a collection of recommended techniques and best practices to use in your projects. Why GSAP? Animating with GSAP gives you unprecedented levels of control and flexibility. You can reach for GSAP to animate everything — from simple DOM transitions to SVG, three.js, canvas or WebGL — your imagination is the limit. More importantly, you can rely on us. We obsess about performance, optimizations and browser compatibility so that you can focus on the fun stuff. We've actively maintained and refined our tools for over a decade and there are no plans to stop. Lastly, if you ever get stuck, our friendly forum community is there to help. Going forward we will assume a comfortable understanding of both GSAP and React. If you're starting out we highly recommend reading our foundational article first - First Steps & Handy Techniques.. Quick Links Component Communication Passing down a timeline prop Passing down callback to build a timeline React Context Imperative Communication Creating Reusable Animations registerEffect() Exit Animations Custom Hooks useSelector useArrayRef useStateRef useIsomorphicLayoutEffect Online Playgrounds Get started quickly by forking one of these starter templates: CodePen CodeSandbox CodeSandbox + Bonus Plugins Component Communication In the last article, we covered creating our first animation, and how to create and control timelines within a React component. But there are times where you may need to share a timeline across multiple components or construct animations from elements that exist in different components. In order to achieve this, we need a way to communicate between our components. There are 2 basic approaches to this. a parent component can send down props, e.g. a timeline a parent component can pass down a callback for the child to call, which could add animations to a timeline. Passing down a timeline prop Note that we are using useState instead of useRef with the timeline. This is to ensure the timeline will be available when the child renders for the first time. function Box({ children, timeline, index }) { const el = useRef(); // add 'left 100px' animation to timeline useEffect(() => { timeline.to(el.current, { x: -100 }, index * 0.1); }, [timeline]); return <div className="box" ref={el}>{children}</div>; } function Circle({ children, timeline, index, rotation }) { const el = useRef(); // add 'right 100px, rotate 360deg' animation to timeline useEffect(() => { timeline.to(el.current, { rotate: rotation, x: 100 }, index * 0.1); }, [timeline, rotation]); return <div className="circle" ref={el}>{children}</div>; } function App() { const [tl, setTl] = useState(() => gsap.timeline()); return ( <div className="app"> <Box timeline={tl} index={0}>Box</Box> <Circle timeline={tl} rotation={360} index={1}>Circle</Circle> </div> ); } See the Pen React Tutorial 3a by GreenSock (@GreenSock) on CodePen. Passing down a callback to build a timeline function Box({ children, addAnimation, index }) { const el = useRef(); // return a 'left 100px' tween useEffect(() => { const animation = gsap.to(el.current, { x: -100 }); addAnimation(animation, index); return () => animation.progress(0).kill(); }, [addAnimation, index]); return <div className="box" ref={el}>{children}</div>; } function Circle({ children, addAnimation, index, rotation }) { const el = useRef(); // return a 'right 100px, rotate 360deg' tween useEffect(() => { const animation = gsap.to(el.current, { rotate: rotation, x: 100 }); addAnimation(animation, index); return () => animation.progress(0).kill(); }, [addAnimation, index, rotation]); return <div className="circle" ref={el}>{children}</div>; } function App() { // define a timeline const [tl, setTl] = useState(() => gsap.timeline()); // pass a callback to child elements, this will add animations to the timeline const addAnimation = useCallback((animation, index) => { tl.add(animation, index * 0.1); }, [tl]); return ( <div className="app"> <Box addAnimation={addAnimation} index={0}>Box</Box> <Circle addAnimation={addAnimation} index={1} rotation="360">Circle</Circle> </div> ); } See the Pen Passing down a callback to build a timeline. by GreenSock (@GreenSock) on CodePen. React Context Passing down props or callbacks might not be ideal for every situation. The component you're trying to communicate with may be deeply nested inside other components, or in a completely different tree. For situations like this, you can use React's Context. Whatever value your Context Provider provides will be available to any child component that uses the useContext hook. const SelectedContext = createContext(); function Box({ children, id }) { const el = useRef(); const selected = useContext(SelectedContext); useEffect(() => { gsap.to(el.current, { // animate x by 200 if the box ID matches the selected context value x: selected === id ? 200 : 0 }); }, [selected, id]); return <div className="box" ref={el}>{children}</div>; } function App() { // Any component can read the value passed to the provider, no matter how deeply nested. // In this example, we're passing "2" as the current value. return ( <SelectedContext.Provider value="2"> <Box id="1">Box 1</Box> <Box id="2">Box 2</Box> <Box id="3">Box 3</Box> </SelectedContext.Provider> ); } See the Pen React Tutorial 3c by GreenSock (@GreenSock) on CodePen. Imperative Communication Passing around props or using Context works well in most situations, but using those mechanisms cause re-renders, which could hurt performance if you're constantly changing a value, like something based on the mouse position. To bypass React’s rendering phase, we can use the useImperativeHandle hook, and create an API for our component. const Circle = forwardRef((props, ref) => { const el = useRef(); useImperativeHandle(ref, () => { // return our API return { moveTo(x, y) { gsap.to(el.current, { x, y }); } }; }, []); return <div className="circle" ref={el}></div>; }); Whatever value the imperative hook returns will be forwarded as a ref function App() { const circleRef = useRef(); useEffect(() => { // doesn't trigger a render! circleRef.current.moveTo(300, 100); }, []); return ( <div className="app"> <Circle ref={circleRef} /> </div> ); } See the Pen React Tutorial 3d by GreenSock (@GreenSock) on CodePen. Creating reusable animations Creating reusable animations is a great way to keep your code clean while reducing your app’s file size. The simplest way to do this would be to call a function to create an animation. function fadeIn(target, vars) { return gsap.from(target, { opacity: 0, ...vars }); } function App() { const box = useRef(); useLayoutEffect(() => { const animation = fadeIn(box.current, { x: 100 }); }, []); return <div className="box" ref={box}>Hello</div>; } For a more declarative approach, you can create a component to handle the animation. function FadeIn({ children, vars }) { const el = useRef(); useLayoutEffect(() => { gsap.from(el.current.children, { opacity: 0, ...vars }); }, []); return <span ref={el}>{children}</span>; } function App() { return ( <FadeIn vars={{ x: 100 }}> <div className="box">Box</div> </FadeIn> ); } See the Pen React Reusable 1 by GreenSock (@GreenSock) on CodePen. If you want to use a React Fragment or animate a function component, you should pass in a ref for the target(s). Using gsap.effects GSAP provides a way to create reusable animations with registerEffect() function GsapEffect({ children, targetRef, effect, vars }) { useLayoutEffect(() => { if (gsap.effects[effect]) { gsap.effects[effect](targetRef.current, vars); } }, [effect]); return <>{children}</>; } function App() { const box = useRef(); return ( <GsapEffect targetRef={box} effect="spin"> <Box ref={box}>Hello</Box> </GsapEffect> ); } See the Pen React Reusable 6 by GreenSock (@GreenSock) on CodePen. Exit animations To animate elements that are exiting the DOM, we need to delay when React removes the element. We can do this by changing the component’s state after the animation has completed. function App() { const boxRef = useRef(); const [active, setActive] = useState(true); const remove = () => { gsap.to(boxRef.current, { opacity: 0, onComplete: () => setActive(false) }); }; return ( <div> <button onClick={remove}>Remove</button> { active ? <div ref={boxRef}>Box</div> : null } </div> ); } See the Pen React fade out 1 by GreenSock (@GreenSock) on CodePen. The same approach can be used when rendering elements from an array. function App() { const [items, setItems] = useState([ { id: 0 }, { id: 1 }, { id: 2 } ]); const removeItem = (value) => { setItems(prev => prev.filter(item => item !== value)); } const remove = (item, target) => { gsap.to(target, { opacity: 0, onComplete: () => removeItem(item) }); }; return ( <div> {items.map((item) => ( <div key={item.id} onClick={(e) => remove(item, e.currentTarget)}> Click Me </div> ))} </div> ); } See the Pen React fade out 2 by GreenSock (@GreenSock) on CodePen. However - you may have noticed the layout shift - this is typical of exit animations. The Flip plugin can be used to smooth this out. In this demo, we’re tapping into Flip’s onEnter and onLeave to define our animations. To trigger onLeave, we have to set display: none on the elements we want to animate out. See the Pen React Flip 2 by GreenSock (@GreenSock) on CodePen. Custom Hooks If you find yourself reusing the same logic over and over again, there’s a good chance you can extract that logic into a custom hook. Building your own Hooks lets you extract component logic into reusable functions. Let's take another look at registerEffect() with a custom hook function useGsapEffect(target, effect, vars) { const [animation, setAnimation] = useState(); useLayoutEffect(() => { setAnimation(gsap.effects[effect](target.current, vars)); }, [effect]); return animation; } function App() { const box = useRef(); const animation = useGsapEffect(box, "spin"); return <Box ref={box}>Hello</Box>; } See the Pen React Reusable 7 by GreenSock (@GreenSock) on CodePen. Here are some custom hooks we've written that we think you may find useful: useSelector Memoises GSAP’s selector utility. see demo on codepen function useSelector() { const ref = useRef(); const q = useMemo(() => gsap.utils.selector(ref), [ref]); return [q, ref]; } Usage: function App() { const [q, ref] = useSelector(); useEffect(() => { gsap.to(q(".box"), { x: 200 }); }, []); return ( <div ref={ref}> <div className="box">Hello</div> </div> ); } useArrayRef Adds refs to an array. see demo on codepen function useArrayRef() { const refs = useRef([]); refs.current = []; return [refs, (ref) => ref && refs.current.push(ref)]; } Usage: function App() { const [refs, setRef] = useArrayRef(); useEffect(() => { gsap.to(refs.current, { x: 200 }); }, []); return ( <div> <div className="box" ref={setRef}>Box 1</div> <div className="box" ref={setRef}>Box 2</div> <div className="box" ref={setRef}>Box 3</div> </div> ); } useStateRef This hook helps solve the problem of accessing stale values in your callbacks. It works exactly like useState, but returns a third value, a ref with the current state. see demo on codepen function useStateRef(defaultValue) { const [state, setState] = useState(defaultValue); const ref = useRef(state); const dispatch = useCallback((value) => { ref.current = typeof value === "function" ? value(ref.current) : value; setState(ref.current); }, []); return [state, dispatch, ref]; } Usage: const [count, setCount, countRef] = useStateRef(5); const [gsapCount, setGsapCount] = useState(0); useEffect(() => { gsap.to(box.current, { x: 200, repeat: -1, onRepeat: () => setGsapCount(countRef.current) }); }, []); useIsomorphicLayoutEffect You might see a warning if you use server-side rendering (SSR) with useLayoutEffect. You can get around this by conditionally using useEffect during server rendering. This hook will return useLayoutEffect when the code is running in the browser, and useEffect on the server. caveat: Any "from" state that doesn't match the server-side rendered HTML/CSS content will still suffer from a flash of unstyled content while the JavaScript is being parsed, run and hydrated. read more about useLayoutEffect and server rendering see demo on codepen const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect; Usage: function App() { const box = useRef(); useIsomorphicLayoutEffect(() => { gsap.from(box.current, { opacity: 0 }); }, []); return ( <div> <div className="box" ref={box}>Hello</div> </div> ); } If there is anything you'd like to see included in this article, or if you have any feedback, please leave a comment below so that we can smooth out the learning curve for future animators. Good luck with your React projects and happy tweening!
  18. Hello, I have a list of component (parents), inside this these components there are others components (children). I have a on hover gsap animation on parents, but when I hover them the children width and height change like there is some propagation, and I just want the parents to be animated not the children. Here is the code, The parents : listSuppTmp.push(<ContainerSuppRef rstart={((res.data[j].U) - parseInt(res.data[j].taille) + 1)} rend={res.data[j].U + 1}> <SupportDiv onMouseEnter={(e) => onEnter(e, res.data[j].ID, res.data[j].taille)} onMouseLeave={(e) => onLeave(e, res.data[j].ID)} key={res.data[j].ID}> { await axios.get(`http://localhost:8888/api/support/cassette-type-conn/${res.data[j].supportID}`) .then(res => { //console.log(res.data) return <ContainerPorts data={res.data}></ContainerPorts> }) } </SupportDiv> <RefDiv ref={el => elemsRef.current[res.data[j].ID] = el}>{res.data[j].support_ref}</RefDiv> </ContainerSuppRef>) (This listSuppTmp.push is in a for loop) The children : function ContainerPorts (props) { if (props.data.type === 'Cuivre') { return ( <> <CuivrePort> <Port/><Port/><Port/><Port/><Port/><Port/> </CuivrePort> <CuivrePort> <Port/><Port/><Port/><Port/><Port/><Port/> </CuivrePort> <CuivrePort> <Port/><Port/><Port/><Port/><Port/><Port/> </CuivrePort> <CuivrePort> <Port/><Port/><Port/><Port/><Port/><Port/> </CuivrePort> </> ) } return (<></>) } So in the children components, CuivrePort is a container of ports which are basically small cubes. On the 3 pictures, the first one is a parent that has not been hovered the 2nd it has been hovered the 3rd is a the normal behavior but empty and I want this with scaled children components inside Thanks in advance
  19. When you use smooth-scrollbar, React and GSAP ScrollTrigger in multiple React components all animations stop working. Firstly I thought it's Gatsby issue but the same thing happens for pure React. Everything works well if you don't use smooth-scrollbar, also everything works well when you use smooth-scrollbar and store all scrollTrigger animations in one useEffect. But when you move them to other components then all animations stop working. Try to use my codepen example. Animations don't work but when you comment out scrollTrigger object from the "Other" component then it starts to work.
  20. I have been trying to create a navbar animation where the navbar comes from top when clicked and goes back the exact same way but so far that is not how my code is behaving. On clicking the back button the navbar simply disappears. Here is the codesandbox link https://codesandbox.io/s/misty-star-ncl3z
  21. Hi Team! I'm trying to setup a simple animation with a boat moving along a path as the user scrolls down the page. I had the boat moving along the path correctly, but then I added the next step which was to pin the whole map so you could actually see the full animation before it scrolls off-screen - and this proved kinda tricky! I went through a few different iterations of path-mismatch, and now the boat does appear to be moving along a line of motion, but it's as if the path is rotated 90 degrees and the boat goes bouncing around towards the left instead of going down the map along the dotted line. I must be calling the motionPath before the asset has loaded or something, right? Any other suggestions where I am going wrong will be massively appreciated! Codepen here (brown box in place of the boat) https://codepen.io/Allanw/pen/jOarMaO
  22. Hello everyone, I am new to using GSAP, so I need a little bit of help here because I am lost on where to start. I found this cool effect (https://rpj.bembi.dev/) that uses this package: react-particles-js. Can anyone instruct me on how to replicate this effect in React using GSAP please? Thank you!
  23. The Timeline play() method with ScrollTrigger is not working after the pin starts. Overview: There are 3 sections(red, blue, yellow). The blue section is pinned with ScrollTrigger. In the blue section there is a cyan box animated with the help of TimeLine(initially the animation is paused). There is button below the cyan box to play the animation/timeline. Steps to check: 1. Scroll to a point so that the blue section is not pinned yet and you can see the cyan box and the "ANIMATE" button. 2. Press the "ANIMATE" button and it should animate as many times as you press the button. 3. Now scroll more into into the blue section so that the blue section gets pinned. 4. Now try to click the "ANIMATE" button and you will see the TimeLine is not playing. The "ANIMATE" button will not work once the pinning starts so reload the page and follow steps 1 and 2 to see that the animation did indeed work befoer the pinning. This maybe a trivial mistake on my part. Thanks for your help.
  24. Hello, i'm trying to do build the same video/image sequence style of the Airpods website and also add text. I googled and found this awesome codepen posted in the GSAP forum (see end of post): I'd like to have something like this codepen (scroll after panel 5) : https://codepen.io/GenSock/pen/bGexQpq?editors=0010 But I really don't know how I can do something like this (add a sort of duration maybe ? Or even "scroll-out" when there is no text left to show ?) Here is an example when I try to simply add another div (see screenshot) : you can see that the canvas is still "playing" (the scroll duration comes from the window height not the canvas height/parent height), and also that is goes way beyond the div. So, my question is: how can I make it so it does not goes beyond the div, and that the canvas duration is related to the div size (after div, the canvas is not changing). Also, I'm doing my website with React, and found this npm package : https://www.npmjs.com/package/react-gsap Do you recommend to use it, or should I stick with "basic" gsap ? Thank you .
  25. Hi GSAPians, I'm working on a landing page using GatsbyJs + GSAP. I've multiple sections in which I wanna apply the animations on scroll using scrollTrigger. so, I wanna know the best practice to register the scrollTrigger plugin when It's has to be used in multiple sections. Do I've to register it in each section's component? OR Is there any way to register it on one place and then use it in any section or component I want? Thanks
×