Jump to content
GreenSock

Rodrigo last won the day on May 2 2021

Rodrigo had the most liked content!

Rodrigo

Moderators
  • Posts

    2,114
  • Joined

  • Last visited

  • Days Won

    168

Everything posted by Rodrigo

  1. I'm not sure I completely understand what you're setup looks like, but hopefully this codepen is somehow close to that: https://codepen.io/rhernando/pen/dyNJREb Happy Tweening!!!
  2. Hi and welcome to the GreenSock forums. There are a few things that are causing the issue. You're adding instances to the timeline inside a useEffect() hook that is called every time the menu is toggled, so basically you're creating the timeline over and over again. The first time the timeline takes the elements and update their styles to what you want, by the second time the timeline is created and it updates the styles to the indicated values, but in the first run the timeline already set those values in the elements, so nothing happens. In this type of situations (toggle animations) is better to move all the code that adds animations to a timeline, to an useEffect hook that runs only in the first render, using an empty dependencies array. Then store the timeline in a specific reference in order to keep it through future re-renders. Finally handle the timeline toggle inside a specific useEffect hook. This seems to be doing what you want: const tl = useRef(gsap.timeline({ defaults: { ease: "power2.inOut" } }).reverse()); const [isMenuOpen, ToggleMenu] = useState(false); useEffect(() => { tl.current.to([activatorRef.current], { background: "#805ad5", borderRadius: "0 5em 5em 0" }); tl.current.to( [navItemContainerRef.current], { clipPath: "ellipse(100% 100% at 50% 50%)" }, "-=.5" ); tl.current.to( [navItemIconRefs.current], { opacity: 1, transform: "translateX(0)", stagger: 0.5 }, "-=.5" ); }, []); useEffect(() => { tl.current.reversed(!isMenuOpen); }, [isMenuOpen]); Happy Tweening!!!
  3. Hi, Your problem could be here: let tl = new TimelineLite({ delay: 0.8 }); useEffect(() => {}, [tl]); You're creating your timeline as a variable in the root scope of the component's function, that will not be kept through re-renders. Assuming that this is a header and you might be using router, every route update there could be a re-render. Also you're passing tl as a dependency in your hook's array, but tl is not a state value or a prop , so that code will run every time the state or a prop is updated. Use an empty array in the useEffect() hook and store a reference to the timeline using useRef(): const tl = useRef(gsap.timeline({ paused: true })); useEffect(() => { tl.current // All your gsap instances here .from() }, []); Try that and let us know how it goes. Happy Tweening!!!
  4. Brian is right, it has to be inside the useEffect or in the componentDidMount hook (if you're using class components). Perhaps what you've seen before is that a reference is being created for a GSAP instance outside a useEffect hook in order to keep it through re-renders and perhaps update it in case of a specific state or prop update: const myTween = useRef(gsap.timeline({ paused: true })); const myRef = useRef(); useEffect(() => { myTween.current.to(myRef.current, { duration: 1, x: 100, y:100 }); return (() => { myTween.current.kill(); }); }, []); Happy Tweening!!!
  5. Hi, I saw that you updated the codepen sample. It was somehow helpful, but I'll get to that later The thing is that the setup of your app is quite unorthodox and a bit difficult to follow. The solution I came up using your code is to pause the animation of the currently visible tab and set it's time to 0, before changing the visible tab. Like that the next time a user goes back to that tab, the element's position will be what you expect and the animation will fire again. This code seems to do that: //refs const containerRef = React.useRef(null); // CREATE A REFERENCE FOR THE TAB ANIMATIONS const tweenRefs = React.useRef([]); // CREATE A REFERENCE FOR THE CURRENT TAB INDEX const currentTab = React.useRef(0); //state const [tabclick, setTabclick] = React.useState(); React.useEffect(() => { const tabs = document.querySelectorAll(".tabs .tab"); const items = containerRef.current.querySelectorAll(".tabbed-content .item"); //toggle tabs tabs.forEach((tab) => { tab.addEventListener("click", (e)=>{ // setTabclick(Math.random()); tabs.forEach((tab) => { tab.classList.remove('active'); }); tab.classList.add('active'); let tabIndex = tab.classList[1].substr(3); // PAUSE THE ANIMATION OF THE CURRENT TAB tweenRefs.current[tabIndex - 1].pause(0); // UPDATE THE REFERENCE OF THE CURRENT TAB INDEX currentTab.current = tabIndex; items.forEach(item => { let itemIndex = item.classList[1].substr(4); if(itemIndex === tabIndex){ item.classList.add("show"); } else { item.classList.remove("show"); } }); }); }); //GSAP animation function animation1(element){ let tl = gsap.timeline(); tl.to( element, { x: 500, duration: 1, ease: "elastic" }); // CREATE A REFERENCE FOR THE ANIMATION tweenRefs.current.push(tl); return tl; } //console.log(items); items.forEach(item => { //console.log(item.classList.contains("show")); if(item.classList.contains('show')){ console.log("item::"); console.log(item.querySelector('.box')); } }); //GSAP SCROLLTRIGGER ScrollTrigger.matchMedia({ "(min-width: 768px)": function() { items.forEach((item, index) => { ScrollTrigger.create({ trigger: '.show .box', start: "center center-=100", end: "bottom top", id: "st-box-" +index, animation: animation1('.show .box'),//animation1(item.querySelector('.box')), markers: true, toggleActions: "play pause resume pause", onRefresh: self => { if (item.offsetHeight === 0) { //console.log(item.offsetHeight); self.animation.pause(0); } else { //console.log(item.offsetHeight); self.animation.progress(0); } } }); }); } }); }, []); // REMOVE DEPENDENCY IN ARRAY, NO NEED FOR THAT Finally, I strongly recommend you to get your feet wet in React, right now it shows that you're just starting with it since you're basically writing vanilla JS inside a React set up and, while it works, you're not taking advantage of some features and strengths the framework has to offer in order to make your life easier as a developer. I don't have time to go through everything that you're not doing in the most adequate way since it would take quite a while. Go to the react documentation and take a look at it, also you could benefit from some free video tutorials that should be quite helpful. This is a short one by Brad Traversy, while it might not cover everything you need to know to work in React (spoiler alert no course does) Brad has a very good and calm teaching approach: https://www.youtube.com/watch?v=w7ejDZ8SWv8 This is a bit longer and perhaps covers more features than Brad's: https://www.youtube.com/watch?v=4UZrsTqkcW4 Happy Tweening!!!
  6. Ok, so I assume that the bundled file is being targeted inside an HTML file, like this: <script src="./app.js"></script> Basically you have to put your script after that tag and both should be before the closing <body> tag: <body> <!-- ALL YOUR HTML HERE --> <script src="./app.js"></script> <script> gsap.from('.about h1', { y: 40, delay: 0.25 }); </script> </body> Also keep in mind where exactly your HTML file resides in order to get the correct path to the app.js file. Happy Tweening!!!
  7. Hi and welcome to the GreenSock forums. What tool are you using to create your bundle (parcel, Create React App, Vue CLI, Gatsby, Next, Nuxt, Svelte, Sapper, etc)? Basically the point is that the script being created by the bundle (which actually takes your code and packages and create a browser-ready file) is being added after the script tag you're adding manually. Happy Tweening!!!
  8. If you have a notion of the image height, or the height you're using in the styles, then add that to the <img> tag in order to avoid layout shifting, which could be the cause of the issue you're mentioning. Perhaps this article can help you: https://www.afasterweb.com/2019/10/30/a-new-way-to-prevent-layout-jank-during-image-load/ Happy Tweening!!!
  9. https://reactjs.org/docs/hooks-effect.html The array in the useEffect hook is a set of dependencies in the component's state or props that will trigger that code to run. Finally as I mentioned before, I recommend you to move your GSAP logic inside each component. As I understand how easy it makes development to have some sort of factory function and I applaud the fact that you created one, it seems to be creating more problems than it's solving. I once worked in a very large app and at some point the project manager stopped almost everything, because some specific methods that would prevent us from writing lots of lines of code, were causing us to create a lot of convoluted conditions and state management in order to make them work. At the end we had to write a lot of code over and over, but the development logic and code maintenance became super easy and there wasn't a noticeable performance drop. Sometimes super elegant code is not the best choice. The code has to do what you need it to do and then you start refine it, keeping in mind that such code at some point will be looked by another developers that never worked in that project and makes sense to them. Happy Tweening!!!
  10. @Cassie Seems like our requests were listened !!! A Flip instance is in fact a GSAP timeline, so yeah, it can be added to another timeline. Happy Flipping!!!
  11. As Brian points, basically you're killing all the ScrollTrigger instances you have. Also your set up a is a little bit funky, IMHO. If I was you I'd create the ScrollTrigger instance on each component in the useEffect() hook, passing an empty array to run the code only in the initial render and store it in a constant or a ref if you prefer. In the same hook, use the cleanup function to kill that specific ScrollTrigger instance, something like this: const myScrollTrigger = useRef(); useEffect(() => { myScrollTrigger.current = gsap.to(contentRef.current, { scrollTrigger: { trigger: triggerRef.current, start: "top center", end: "bottom center", scrub: true, markers: true, }, yoyo: true, repeat: 1, opacity: 1, y: 0, }); return (() => { myScrollTrigger.current.kill(); }); }, []); Of course you have to keep in mind the trigger element and pass that either as a prop or perhaps context, depending on the depth of your component's tree. Happy Tweening!!!
  12. I can't keep up with your speed coding !!!
  13. Hi, This code seems to do what you're after: $('.shuffle').click(function () { // event.preventDefault(); $('.card').addClass('active'); $('.bet').css('pointer-events', 'none'); $('.grid .card').css('pointer-events', 'auto'); }); const grid = document.querySelector(".grid"); const items = gsap.utils.toArray(".rg_item"); let repeatCount = 0; const repeatLimit = 3; const Animate = function() { if (repeatCount === repeatLimit) return repeatCount = 0; repeatCount += 1; // Get the state const state = Flip.getState(items); // Do the actual shuffling for(let i = items.length; i >= 0; i--) { grid.appendChild(grid.children[Math.random() * i | 0]); } // Animate the change Flip.from(state, { absolute: true, delay: .2, duration: .4, onComplete: Animate }); }; document.querySelector(".shuffle").addEventListener("click", Animate); Also @GreenSock and @OSUblake, can a flip instance be placed inside a timeline? It would be nice for a scenario like this. Just create a method that returns a Flip instance and it's added to a timeline. Happy Tweening!!! Mhhh.... seems like @Cassie was faster than me!!!
  14. Hi, Can you create a live reduced sample, showing just what's causing your issues, in codesandbox? Just select create a new sandbox using the regular react template and then add gsap as a dependency. With the information you have provided so far is quite difficult to see what could be the issue. Happy Tweening!!!
  15. Hi, In the codepen you provided the url for the video points to a relative path: <video src="/images/homepage/bond-motion.mp4"></video> That's why there's nothing there. You have to upload your video to codepen. Go to the bottom left corner and click in the Assets button, upload the video and then get the URL for it and use that in the src attribute of your video tag. Also I took a look at the live site and I have a question: Do you have any chance of making that video an SVG? That is actually screaming SVG all over the place, for me at least. Honestly I haven't worked a lot with videos, but I'm not sure how smooth you can get a video progression by updating it's current position programmatically, that's why I'm thinking SVG. Perhaps @Cassie and @PointC have more experience with video and could point you in the right direction. Happy Tweening!!!
  16. Hi and welcome to the GreenSock forums. I'm not seeing any animation playing at any point. Also your sample doesn't have any scrolling available so we can't test that neither. If you want to control the animation of the content of each tab, I'd suggest you to loop through those elements and create a Scroll Trigger for each one, also put each tab in it's own component in order to better control each one's Scroll Trigger instance. Right now you're creating a scroll trigger for this selector: animation1('.show .box'), which is just the current active box and you're doing in it in a useEffect hook that runs on the main component's initial render. As I said, move each tab content element to it's own component and create the scroll trigger instance in there. Finally create some scrolling space on each one in order to see what is and is not working and why. Unfortunately I don't have time right now to create a working sample for you, so hopefully this helps in some way. Happy Tweening!!!
  17. You don't have to remove the panels constant definition, you have to replace this: gsap.to(panels, { xPercent: -100 * ( panels.length - 1 ), ease: "none", scrollTrigger: { trigger: "#panels-container", pin: true, start: "top top", scrub: 1, snap: { snapTo: 1 / ( panels.length - 1 ), duration: {min: 0.1, max: 0.1} }, end: () => "+=" + (panelsContainer.offsetWidth - innerWidth) } }); With this: const tl = gsap.timeline({ paused: true, scrollTrigger: { trigger: "#panels-container", pin: true, start: "top top", scrub: 0.5, snap: { snapTo: (1/1.1) / ( panels.length - 1 ), duration: {min: 0.1, max: 0.1} }, end: () => "+=" + (panelsContainer.offsetWidth - innerWidth) } }) .to(panels, { xPercent: -100 * ( panels.length - 1 ), ease: "none", duration: 1 }) .to({}, {duration: 0.1}, 1); Also you can fork and create your own codepen sample, take a look at this: Finally knowing and being able to handle certain degree of javascript is fundamental in web development, unless this is some sort of homework assignment for school, so I'd strongly recommend you to look around for some learning resources like these: https://www.youtube.com/watch?v=PkZNo7MFNFg https://www.youtube.com/watch?v=hdI2bqOjy3c Happy Tweening!!!
  18. Hi, Perhaps this tutorials by @PointC could be a good starting point: https://www.motiontricks.com/animated-handwriting-effect-part-1/ https://www.motiontricks.com/svg-calligraphy-handwriting-animation/ Happy Tweening!!!
  19. Hi, I don't see anything React-related that could be causing this issue. Also @akapowl's solution seems to do the trick, since the source of your problem seems to be the CSS of your app. Happy Tweening!!!
  20. Hi Jim and welcome to the GreenSock forums. This seems to be a combination of factors. The issue is that as soon as the scroll position reaches the end the pin is released, but since the Scroll Trigger instance has a scrub value set to 1 the animation is not yet completed when that happens. One alternative is to change the animation to a timeline and add a short dummy instance at the end of the timeline and make the scrub value smaller, in order to have some extra scrolling space and prevent the jump. The downside of this is that the snap function goes crazy and stops working as expected, so we have to tinker with it a bit. This code seems to work pretty well: const tl = gsap.timeline({ paused: true, scrollTrigger: { trigger: "#panels-container", pin: true, start: "top top", scrub: 0.5, snap: { snapTo: (1/1.1) / ( panels.length - 1 ), duration: {min: 0.1, max: 0.1} }, end: () => "+=" + (panelsContainer.offsetWidth - innerWidth) } }) .to(panels, { xPercent: -100 * ( panels.length - 1 ), ease: "none", duration: 1 }) .to({}, {duration: 0.1}, 1); Give it a shot and let us know how it goes. Happy Tweening!!!
  21. Hi and welcome to the GreenSock forums. Perhaps this thread can help you getting started with creating a Codepen demo: It doesn't have to be a full demo of your app, just a reduced case that allows us to see the part of the code that is giving you trouble. Happy Tweening!!!
  22. Hi and welcome to the GreenSock forums. As far as I know, once you add an infinite instance inside a timeline that timeline's duration becomes infinite as well. As much as @GreenSock has spent many hours bending the construct of time-space (for example you can pass a negative time to a GSAP instance), I'm afraid He hasn't gone that far, yet. We have a new admin in the house that might be able to crack that In a serious note now, what you can do is add a label in a certain position and get that label's time, like that you'll have a notion of the duration of the timeline until that point: https://greensock.com/docs/v3/GSAP/Timeline/labels Another alternative is to combine the label time plus the duration of the infinite timeline, it has to be duration and not total duration: https://greensock.com/docs/v3/GSAP/Timeline/duration() I also saw that @Carl beat me to the punch here, so you can do what He said too Happy Tweening!!!
  23. Rodrigo

    MorphSVG in ReactJS

    Inkscape also can combine paths into a compound path: https://inkscape.org/doc/tutorials/advanced/tutorial-advanced.html You can download one or more files that are ready to be used: https://www.freepik.com/search?dates=any&format=search&page=2&query=devices&selection=1&sort=popular&type=vector Happy Tweening!!!
  24. Rodrigo

    MorphSVG in ReactJS

    Nope a path can't have more than one d attribute. Honestly is quite unclear to me what you're trying to do, beyond the fact that you want to morph some SVG paths, this because you haven't been able to provide a working sample. I encourage you to get your feet wet with SVG first, since you have some glaring issues with it and how it works. For example the fact that you added several d attributes to a single path, the fact that your svg tag doesn't have a viewbox and your paths are not visible right now. Also the path you're trying to morph right now is inside a <defs> tag. Also you didn't included the link Blake provided for the test file of the MorphSVG Plugin. This code in the morphAnimation.js file seems to do something, but I'm not sure if that's what you're after: import React, { useRef, useEffect } from "react"; import { gsap } from "gsap"; gsap.registerPlugin(window.MorphSVGPlugin); const finalPath = "M2883.73851,14.7479675 C2944.77177,14.7479675 2994.26316,64.3124718 2994.26316,125.427935 L2994.26316,1688.57207 C2994.26316,1749.70102 2944.76552,1799.25203 2883.70168,1799.25203 C2879.54668,1799.25203 2876.24772,1802.55348 2876.24772,1806.62602 C2876.24772,1810.69856 2879.54668,1814 2883.61614,1814 C2952.90552,1814 3009,1757.84503 3009,1688.57207 L3009,125.427935 C3009,56.1697756 2952.91308,0 2883.73851,0 L2736.36842,0 C2732.29895,0 2729,3.30144497 2729,7.37398374 C2729,11.4465225 2732.29895,14.7479675 2736.36842,14.7479675 L2883.73851,14.7479675Z"; // The path you want to morph to const MorphAnimation = () => { const targetPath = useRef(); useEffect(() => { gsap.to(targetPath.current, { duration: 2, repeat: -1, yoyo: true, morphSVG: finalPath }); }, []); return ( <div> <svg width="100%" viewBox="0 0 3500 2500"> <path ref={targetPath} d="M1206,151 C1206,891 1206,1631 1206,2371 1206,2451.63357 1140.63357,2517 1060,2517 757,2517 454,2517 151,2517 70.36643,2517 5,2451.63357 5,2371 5,1631 5,891 5,151 5,70.36643 70.36643,5 151,5 454,5 757,5 1060,5 1140.63357,5 1206,70.36643 1206,151z" /> </svg> </div> ); }; export default MorphAnimation; Try playing with the MorphSVG Plugin in a regular codepen first, before jumping to a React app. You can use this starter pen from the GreenSock collection: https://codepen.io/GreenSock/pen/RxBOrb Happy Tweening!!!
  25. Rodrigo

    MorphSVG in ReactJS

    Sorry I don't have one, but just follow the same approach for a regular MorphSVG sample and store the path you want to morph using a useRef hook. Finally you don't need to import the whole SVG file, all you need is the d attribute of the element you want, the rest is not really necessary. Something like this: import React, { useRef, useEffect } from "react"; import { gsap } from "gsap"; gsap.registerPlugin(window.MorphSVGPlugin); const finalPath = "..."; // The path you want to morph to const App = () => { const targetPath = useRef(); useEffect(() => { gsap.to(targetPath.current, { duration: 2, repeat: -1, yoyo: true, morphSVG: finalPath }); }, []); return ( <div> <svg> <path ref={targetPath} d="..." /> </svg> </div> ); }; Happy Tweening!!!
×