Jump to content
Search Community

Rodrigo last won the day on April 28

Rodrigo had the most liked content!

Rodrigo

Administrators
  • Posts

    6,653
  • Joined

  • Last visited

  • Days Won

    287

Posts posted by Rodrigo

  1. Hi @ajk and welcome to the GSAP Forums!

     

    First proper cleanup is very important with frameworks, but especially with React. React 18 runs in strict mode locally by default which causes your useEffect() and useLayoutEffect() to get called TWICE.

     

    Since GSAP 3.12, we have the useGSAP() hook (the NPM package is here) that simplifies creating and cleaning up animations in React (including Next, Remix, etc). It's a drop-in replacement for useEffect()/useLayoutEffect(). All the GSAP-related objects (animations, ScrollTriggers, etc.) created while the function executes get collected and then reverted when the hook gets torn down.

     

    Here is how it works:

    const container = useRef(); // the root level element of your component (for scoping selector text which is optional)
    
    useGSAP(() => {
      // gsap code here...
    }, { dependencies: [endX], scope: container }); // config object offers maximum flexibility

    Or if you prefer, you can use the same method signature as useEffect():

    useGSAP(() => {
      // gsap code here...
    }, [endX]); // simple dependency Array setup like useEffect()

    This pattern follows React's best practices.

     

    We strongly recommend reading the React guide we've put together at https://gsap.com/resources/React/

     

    Finally in the case of your TSX demo, the different is that you're using snapping in your ScrollTrigger config, while in your JS demo you're not:

    scrollTrigger: {
      trigger: descContRef.current,
      pin: true,
      scrub: 1,
      snap: 1 / (totalPanels - 1),// Here
      // base vertical scrolling on how wide the container is so it feels more natural.
      end: () => `+=${descContRef.current?.offsetWidth}px`,
    },

    If you remove that line, most likely this will work the way you intend.

     

    Hopefully this helps.

    Happy Tweening!

  2. Hi,

     

    Just to add to @mvaneijgen's great advice, you can create a new NextJS project in Stackblitz. Click on the large New Project button at the top left and select NextJS in the FullStack tab:

    Hb8atUM.png

    The one thing we ask to our users is to keep the demo as small and simple as possible, just the easiest way to re-create the probem you're facing.

     

    Also in ScrollTrigger you can set a scroller property if you don't want to use the window object as the default scroller. From the ScrollTrigger docs:

    scroller
    String | Element - By default, the scroller is the viewport itself, but if you'd like to add a ScrollTrigger to a scrollable <div>, for example, just define that as the scroller. You can use selector text like "#elementID" or the element itself.

     

    https://gsap.com/docs/v3/Plugins/ScrollTrigger/#config-object

     

    Hopefully this helps.

    Happy Tweening!

    • Like 1
  3. Hi @fejmek and welcome to the GSAP Forums!

     

    Sure thing, you can definitely use MorphSVG for something like that. @mvaneijgen on of the forums superheroes created this thread where He shares a lot of knowledge on the subject:

    Finally in your demo keep in mind that you're not using the correct syntax for a circular clip path:

    // Wrong
    timeline.from(circle, {
      clipPath: "circle(0%)",
      stagger: 1,
    });
    
    // Right
    timeline.from(circle, {
      clipPath: "circle(0% at 50% 50%)",
      stagger: 1
    });

    Hopefully this helps.

    Happy Tweening!

    • Like 1
  4. Hi,

     

    Basically your ScrollTrigger import is wrong:

     

    // Wrong
    import scrolltrigger from "https://esm.sh/scrolltrigger";
    
    // Right
    import ScrollTrigger from "https://esm.sh/gsap/ScrollTrigger";

    ScrollTrigger  is part of the GSAP package, not an individual separate package and is PascalCased, you had it in lower case.

     

    Hopefully this helps.

    Happy Tweening!

  5. The useGSAP hook has a dependency for at least React 16, but it doesn't use/install React 16:

      "dependencies": {
        "gsap": "^3.12.5",
        "react": ">=16"
      },

    If you already have React 18 installing the @gsap/react package won't change a thing and won't install another version of React.

     

    I just tested this on a demo here on my local machine using the latest version of Next without any issues. We only encountered some issues while importing the @gsap/react package from esm.sh, which solves by doing this:
     

    import React from "https://esm.sh/react@18.3.1";
    import ReactDOM from "https://esm.sh/react-dom@18.3.1";
    
    import gsap from "https://esm.sh/gsap";
    import { useGSAP } from "https://esm.sh/@gsap/react?deps=react@18.3.1";

    We'll update the dependencies on the hook to be at least version 17 of React.

     

    Finally it would be super helpful if you could create a minimal demo that reproduces this error.

     

    Happy Tweening!

    • Like 1
  6. Mhh... Why are you using intersection observer for something that can be done with ScrollTrigger?

     

    I think you are overcomplicating this quite a bit. If I was you  I'd create an extra prop for ScrollTrigger and if that prop has a ScrollTrigger configuration, you just need to check if is not falsy, instead of using just SplitText use ScrollTrigger to handle when the animation plays, something like this:

    See the Pen abxMRgp by GreenSock (@GreenSock) on CodePen

     

    I updated the demo I posted before so it uses ScrollTrigger

    https://stackblitz.com/edit/nuxt-starter-vzsxyp?file=app.vue

     

    The only detail is that this check is not needed with ScrollTrigger:

    completed && tween.progress(1);

    Hopefully this helps.

    Happy Tweening!

    • Like 1
  7. Hi,

     

    You also posted in this other thread:

    Where Jack suggested using ScrollTrigger's sort() method for solving this:

    https://gsap.com/docs/v3/Plugins/ScrollTrigger/static.sort()/

     

    This works as expected:

    sectionB.forEach((section) => {
      
       const textElement = section.querySelector("span");
      //   Load some data, then initialize animation
      later(3000).then(() => {
        console.log("Loaded!!");
    
        const tl = gsap.timeline({
          scrollTrigger: {
            trigger: section,
            start: "top top",
            end: `+=2000`,
            markers: true,
            scrub: true,
            pin: true
          }
        });
    
        tl.to(textElement, {
          scale: 1.2,
          ease: "power1.inOut"
        });
        // Sort the ScrollTrigger instances
        ScrollTrigger.sort();
      });
    });

    This happens because the ScrollTrigger are not created in the order they appear on the screen, if that is not possible we recommend using the refreshPriority configuration in combination with the sort() method, from the ScrollTrigger docs:

    refreshPriority
    number - it's VERY unlikely that you'd need to define a refreshPriority as long as you create your ScrollTriggers in the order they'd happen on the page (top-to-bottom or left-to-right)...which we strongly recommend doing. Otherwise, use refreshPriority to influence the order in which ScrollTriggers get refreshed to ensure that the pinning distance gets added to the start/end values of subsequent ScrollTriggers further down the page (that's why order matters). See the sort() method for details. A ScrollTrigger with refreshPriority: 1 will get refreshed earlier than one with refreshPriority: 0 (the default). You're welcome to use negative numbers too, and you can assign the same number to multiple ScrollTriggers.

     

    Hopefully this helps.

    Happy Tweening!

  8. 23 minutes ago, xtinepak said:

    One thing I'm noticing is that I've to scroll multiple times to change the slides between the sections. For example when the swiper section starts I've to scroll 3 or 4 times to go to the next section. Could you please let me know what needs to be done here?

    It takes only one wheel/touch event, but the previous animation  has to be completed first and we want to wait for some specific situations:

    let allowScroll = true; // sometimes we want to ignore scroll-related stuff, like when an Observer-based section is transitioning.
    let scrollTimeout = gsap.delayedCall(1, () => allowScroll = true).pause(); // controls how long we should wait after an Observer-based animation is initiated before we allow another scroll-related action

     

    3 minutes ago, xtinepak said:

    if we could add fade in/out effect for the swiper section instead of scroll that would be excellent?

    Sure thing, just tinker with the logic in the gotoPanel method, that's where everything happens in terms of animations:

    function gotoPanel(index, isScrollingDown) {
      // return to normal scroll if we're at the end or back up to the start
      if ((index === swipePanels.length && isScrollingDown) || (index === -1 && !isScrollingDown)) {
        intentObserver.disable(); // resume native scroll
        return;
      }
      allowScroll = false;
      scrollTimeout.restart(true);
    
      let target = isScrollingDown ? swipePanels[currentIndex] : swipePanels[index];
      gsap.to(target, {
        yPercent: isScrollingDown ? -100 : 0,
        duration: 0.75
      });
    
      currentIndex = index;
    }

    You can switch yPercent for opacity/autoAlpha without any issues.

     

    Hopefully this clear things up.

    Happy Tweening!

  9. No problemo! Also in these forums there are no stupid questions 👍

     

    That's just the Logical AND operator:

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND

     

    Basically it checks if timer is truthy, if it is it'll clear the timeout. By default the timer variable is undefined which will return falsy and the clearTimeout won't be executed, same with the completed boolean. I want to set the progress of the tween to 1 only if the tween has run completely.

     

    Hopefully this clear things up.

    Happy Tweening!

    • Like 1
  10. Hi,

     

    The issue is in this funky useEffect hook  you have here:
     

    useEffect(() => {
      setInterval(() => {
        setState(Math.random());
      }, 1500);
    });

    That basically runs everytime the component is re-rendered since it has no dependencies array, but at the same time each time the interval is completed the state is updated, creating a new interval and running your custom hook again, so after the first interval is completed a new one is created and each new interval will create a new one, so this grows exponentially.

     

    Here is a fork of your demo without that and it has only two onLeave callbacks, which makes sense since the effect hooks are called twice on StrictMode:

    https://stackblitz.com/edit/react-pps835?file=src%2FApp.jsx

     

    Hopefully this clear things up.

    Happy Tweening!

  11. Hi,

     

    Actually in the demo the columns have a different height, the thing is that they are being animated in opposite directions.

     

    Here is a fork of the same demo with both columns animating in the same direction:

    See the Pen mdgoKVm by GreenSock (@GreenSock) on CodePen

     

    Finally this is most about the calculations needed to move each column on a given direction.

     

    Hopefully this clear things up.

    Happy Tweening!

  12. Hi @fraserYT and welcome to the GSAP Forums!

     

    You are using an extremely generic selector. I'd strongly recommend you to use a unique class in your elements even if it doesn't have any styles, just for selecting purposes. This seems to work the way you expect:

    <div class="header-icons">
      <div class="menu-item"></div>
      <div class="menu-item"></div>
      <div class="menu-item"></div>
      <div class="menu-item"></div>
      <div class="menu-item"></div>
      <div class="menu-item"></div>
    </div>
    gsap.to(".header-icons .menu-item", {
      opacity: 0,
      y: -80,
      stagger: 0.1,
      scrollTrigger: {
        trigger: ".full-row",
        start: 0,
        end: 230,
        scrub: 0.5,
        markers: true
      }
    });

    Here is a fork of your demo:

    See the Pen oNOVqKm by GreenSock (@GreenSock) on CodePen

     

    Hopefully this helps.

    Happy Tweening!

    • Like 1
  13. Hi,


    The video in your demo is over 130MB and it's dimensions are huge. As Cassie mentions rendering the video alone doesn't create any problems but together with other stuff, it can definitely become an issue.

     

    If you want to create some type of particles moving background and you want excellent performance I'd recommend PIXI's particle container:

    https://pixijs.com/8.x/examples/basic/particle-container

     

    You can check this demos by @OSUblake

    See the Pen BjNZYP by osublake (@osublake) on CodePen

     

    See the Pen PjrbWq by osublake (@osublake) on CodePen

     

    Happy Tweening!

    • Like 1
  14. Ahh yeah I see the problem now. Roughly this is your HTML

    <div class="scroll-through-hero st-hero-animate sth-fadein-off" style="background-color: inherit;"></div>
    <div class="immersive-scroll-video" style="background-color: inherit;"></div>
    <div class="immersive-scroll-video" style="background-color: inherit;"></div>
    <div class="scroll-through-hero st-hero-animate sth-fadein-off" style="background-color: inherit;"></div>

    In your JS you're creating the ScrollTrigger instances for the elements inside the immersive-scroll-video elements and then for the scroll-through-hero elements. When using ScrollTrigger ideally create the instances in the order they appear on the screen as the user scrolls down, especially if you are pinning one or more ScrollTrigger instances.

     

    If you can't create your instances in the order they appear in the screen, then you can use the refreshPriority config option. From the ScrollTrigger docs:

    refreshPriority
    number - it's VERY unlikely that you'd need to define a refreshPriority as long as you create your ScrollTriggers in the order they'd happen on the page (top-to-bottom or left-to-right)...which we strongly recommend doing. Otherwise, use refreshPriority to influence the order in which ScrollTriggers get refreshed to ensure that the pinning distance gets added to the start/end values of subsequent ScrollTriggers further down the page (that's why order matters). See the sort() method for details. A ScrollTrigger with refreshPriority: 1 will get refreshed earlier than one with refreshPriority: 0 (the default). You're welcome to use negative numbers too, and you can assign the same number to multiple ScrollTriggers.

     

    Here is the docs for the sort method:
    https://gsap.com/docs/v3/Plugins/ScrollTrigger/static.sort()

     

    Hopefully this helps.

    Happy Tweening!

  15. Hi,

     

    I'm not 100% sure of what issue you're experiencing, but waiting for 1 tick of GSAP's ticker should be enough. As far as I can tell, after you revert your GSAP instances and create the new ones, it should take 1 tick to render the new ones, so that should be enough. Based on your demo this is how I would proceed:

    const tweenable = document.getElementById('tweenable')
    
    let tween, timer;
    
    const createTween = () => {
       tween = gsap.timeline().fromTo(tweenable, {opacity: 1}, {opacity: 0, duration: 1});
    };
    
    const revertTween = () => {
      tween && tween.revert();
      createTween();
    };
    
    window.addEventListener("resize", () => {
      timer && clearTimeout(timer);
      timer = setTimeout(() => {
        revertTween();
      }, 200);
    });
    
    createTween();

    Here is a fork of your demo:

    See the Pen oNOVzdg by GreenSock (@GreenSock) on CodePen

     

    Hopefully this helps.

    Happy Tweening!

×
×
  • Create New...