Jump to content
GreenSock

Rodrigo last won the day on May 30

Rodrigo had the most liked content!

Rodrigo

Administrators
  • Posts

    4,161
  • Joined

  • Last visited

  • Days Won

    222

Posts posted by Rodrigo

  1. Hi @Brian_Open and welcome to the GreenSock forums!

     

    I don't think container animation is what you're looking for actually. If you check the example you posted the faster horizontal animation is not linked to the scroll bar but to the wheel event.

     

    I think a far better approach is to have a single timeline that has both horizontal animations, each with a different duration and also different number of repeats so the total time of each loop is the same. So the faster animation is 3 seconds and repeats 9 times (30 seconds, remember that the first run counts as a repeat ;))  and the slower is 10 seconds and repeats 2 times (30 seconds) . Here is a super simple example of this approach:

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

     

    Is worth noticing that this approach should allow you to use the container animation if you want/need to use that feature.

     

    Hopefully this helps.

    Happy Tweening!

    • Like 1
  2. Hi @Harry J and welcome to the GreenSock forums!

     

    I don't think the last example in this thread is designed to work the way you intend. Maybe a better option would be to use the Horizontal Loop function helper with a small update I made to it in order to change the direction based on the drag direction:

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

     

    Now this example has some small changes I added to the helper function, so I can't guarantee that this is 100% bullet proof. In the tests I ran it seems to hold water. Here you can see the original version:

    https://greensock.com/docs/v3/HelperFunctions#loop

     

    Hopefully this helps.

    Happy Tweening!

  3. Hi,

     

    Yeah for some reason codesandbox is being weird, it wouldn't be the first time 🤷‍♂️

     

    Instead of finding a way to make the sandbox link work I ported the example to Stackblitz:

    https://stackblitz.com/edit/stackblitz-starters-91fcze?description=A create-react-app project based on react and react-dom&file=src%2FApp.js&title=React Starter

     

    I recommend you that for future questions just use Stackblitz, far more stable and less troubles than in codesandbox.

     

    Happy Tweening!

  4. Hi,

     

    You have an odd setup IMHO. Why you have a useEffect listening for a change in a ref when you could just create a method and call it from the onComplete callback. Also right now you're toggling the isTweening value twice, when you animate the texts and when you animate the slides. 

     

    I think this is a far better approach that uses GSAP Context correctly in order to properly cleanup all your GSAP instances in case the component is unmounted:

    https://codesandbox.io/s/withered-cookies-oe6smp?file=/src/App.js

     

    Hopefully this helps.

    Happy Tweening!

  5. Hi,

     

    This is a far more complicated logic issue. The problem here is that at certain points the Y position of the rec is the same while the progress of the timeline keeps increasing as you scroll. This generates odd values for this:

    gsap.ticker.add(() =>
      gsap.to(section_2_container, {
        x: 0,
        y: -(
          gsap.getProperty(rec, "y") -
            ((window.innerHeight - rec.clientHeight) * section_2.progress())
        ) 
      })
    );

    I tried different approaches for this, taking into account the Y position of the element in order to not move the container, but all of them resulted in space at the bottom of the element, which is the initial problem you had (I remember the other thread you created about this).

     

    I think the best course of action is a different approach for this:

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

     

    You can also explore the custom ease helper function by switching the ease option in the config object by changing it to this:

    // immediateRender: true,
    ease: pathEase(path),

     

    This might not be exactly what you need, but solving the issue you have in your current setup is a custom logic work that's beyond the scope of what we can do in these free forums. Unfortunately we don't have the time resources to provide that kind of custom work for our users. You can contact us for a consulting work or post in the Jobs & Freelance forums if you want.

     

    Hopefully this helps.

    Happy Tweening!

  6. Hi,

     

    The only thing I can spot is this:

    if (splits.length) {
      splits.forEach((split, idx) => {
        split.revert();
        if (tl) {
          split.elements.forEach((el) => {
            gsap.set(el, {
              clearProps: true
            });
          });
        }
      });
    }

    No need to do clearProps after the revert method. The revert method does that for you, so this will have the same effect:

    if (splits.length) {
      splits.forEach((split, idx) => {
        split.revert();
      });
    }

    Also this shouldn't have any effect whatsoever:

    const tl = gsap.timeline({
      pause: true,
      scrollTrigger: {
        trigger: rowJobs,
        toggleActions: "play pause resume reset",
        start: "25% bottom",
        markers: true,
      },
      onStart: () => {
        // refresh scrollTrigger on page load to ensure correct scrollTrigger start/end
        tl.scrollTrigger.refresh();
      }
    });

    I don't see the use of that particular refresh method there, when the timeline that has that particular ScrollTrigger config starts, to refresh it's own ScrollTrigger instance. ScrollTrigger already watches for resize events so there is no need for that, if that's what you're aiming for.

     

    Other than that your code is quite clear.

    Hopefully this helps.

    Happy Tweening!

  7. Hi,

     

    I would start by tapping into the snap function in Draggable. Also keep in mind that enabling inertia expands the configuration options of Draggable. From the docs:

    If inertia: true is defined, you may also use any of the following configuration properties that apply to the movement after the mouse/touch is released:

    • snap : [Function | Object | Array] - Allows you to define rules for where the element can land after it gets released. For example, maybe you want the rotation to always end at a 90-degree increment or you want the x and y values to be exactly on a grid (whichever cell is closest to the natural landing spot) or maybe you want it to land on a very specific value. You can define the snap in any of the following ways:
      • As a function - This function will be passed one numeric parameter, the natural ending value. The function must return whatever the new ending value should be (you run whatever logic you want inside the function and spit back the value). For example, to make the value snap to the closest increment of 50, you’d do snap: function(endValue) { return Math.round(endValue / 50) * 50; }.
      • As an Array - If you use an array of values, InertiaPlugin will first plot the natural landing position and then loop through the array and find the closest number (as long as it’s not outside any bounds you defined). For example, to have it choose the closest number from 10, 50, 200, and 450, you’d do snap: [10,50,200,450].
      • As an object - If you’d like to use different logic for each property, like if type is "x,y" and you’d like to have the x part snap to one set of values, and the y part snap to a different set of values, you can use an object that has matching properties, like: snap:{x: [5,20,80,400], y: [10,60,80,500]} or if type is "top,left" and you want to use a different function for each, you could do something like snap: {top: function(endValue) { return Math.round(endValue / 50) * 50; }, left: function(endValue) { return Math.round(endValue / 100) * 100; }}. You can define a points property inside this object that combines both x and y, like liveSnap: {points: [{x: 0, y: 0},{x: 100, y: 0}], radius: 20} which will snap to any point in the array when it’s within 20px (distance). Or you can even use a function-based value to run your own snapping logic, like liveSnap: {points: function(point) { //run custom logic and return a new point }}. See the snapping section of this page for examples.
    • onThrowUpdate : Function - A function that should be called each time the InertiaPlugin tween updates/renders (basically on each “tick” of the engine while the tween is active). This only applies to the tween that gets generated after the user releases their mouse/touch - the function is not called while the user is dragging the element (that’s what onDrag is for). By default, the scope of the onThrowUpdate is the Draggable instance itself, but you may define an callbackScope if you prefer, just like any other tween.
    • onThrowComplete : Function - A function that should be called when the InertiaPlugin tween finishes. This only applies to the tween that gets generated after the user releases their mouse/touch - the function is not called immediately when the user releases their mouse/touch - that’s what onDragEnd is for. By default, the scope of the onThrowComplete is the Draggable instance itself, but you may define an callbackScope if you prefer, just like any other tween.
    • throwResistance : Number - A number (1000 by default) that controls how much resistance or friction there is when the mouse/touch is released and momentum-based motion is enabled (by setting inertia: true). The larger the number, the more resistance and the quicker the motion decelerates. (requires InertiaPlugin and setting inertia: true, otherwise throwResistance will simply be ignored.)
    • maxDuration : Number - The maximum duration (in seconds) that the kinetic-based inertia tween can last. InertiaPlugin will automatically analyze the velocity and bounds and determine an appropriate duration (faster movements would typically result in longer tweens to decelerate), but you can cap the duration by defining a maxDuration. The default is 10 seconds. This has nothing to do with the maximum amount of time that the user can drag the object - it’s only the inertia tween that results after they release the mouse/touch. (requires InertiaPlugin and setting inertia: true, otherwise maxDuration will simply be ignored.)
    • minDuration : Number - The minimum duration (in seconds) that the kinetic-based inertia tween should last. InertiaPlugin will automatically analyze the velocity and bounds and determine an appropriate duration (faster movements would typically result in longer tweens to decelerate), but you can force the tween to take at least a certain amount of time by defining a minDuration. The default is 0.2 seconds. This has nothing to do with the minimum amount of time that the user can drag the object - it’s only the inertia tween that results after they release the mouse/touch. (requires InertiaPlugin and setting inertia: true, otherwise minDuration will simply be ignored.)
    • overshootTolerance : Number - Affects how much overshooting is allowed before smoothly returning to the resting position at the end of the tween. This can happen when the initial velocity from the flick would normally cause it to exceed the bounds/min/max. The larger the overshootTolerance the more leeway the tween has to temporarily shoot past the max/min if necessary. The default is 1. If you don’t want to allow any overshooting, you can set it to 0.

     

    Maybe something like this could provide a good starting point:

    Draggable.create('.wheel', {
      type: 'rotation',
      inertia: true,
      throwResistance: 5000,
      maxDuration: 1.5,
      snap: function(value) {
        return Math.round(value / 10) * 10;
      },
    })

    Hopefully this helps getting started.

    Happy Spinning!

    • Like 1
  8. Hi,

     

    This is just how you should run your effect hooks in React when getting async data or doing async operations. You already have a your data in a hook so that's a good start. Just make your tag list a dependency in the layout effect and it works:

    const { tagsList } = useFetchProducts();
    
    useLayoutEffect(() => {
      if(tagsList.length === 0) return;
      let ctx = gsap.context(() => {
        gsap.set(".text", { xPercent: 50, autoAlpha: 0 });
        gsap.to(".text", {
          xPercent: 0,
          duration: 1,
          stagger: 0.2,
          autoAlpha: 1
        });
      }, HeroRef); // <- scopes all selector text to the root element
    
      return () => ctx.revert();
    }, [tagsList]);

    Hopefully this helps.

    Happy Tweening!

  9. Hi,

     

    You can store the duration of the horizontal section tween and get the amount of the slides in order to get a more dynamic approach:

    const horizontalDuration = 2;
    // Pinning and horizontal scrolling
    let scrollTween = gsap
    .timeline({
      scrollTrigger: {
        scrub: true,
        trigger: sec,
        pin: sec,
        start: "center center",
        end: () => `+=${pinWrapWidth}`,
        invalidateOnRefresh: true
      }
    })
    .to(pinWrap, {
      x: () => -horizontalScrollLength,
      ease: "none",
      duration: horizontalDuration
    })
    .to(
      anchors[0],
      {
        autoAlpha: 0,
        duration: 0.1
      },
      horizontalDuration / slides.length
    )
    .to(
      anchors[1],
      {
        autoAlpha: 1,
        duration: 0.1
      },
      (horizontalDuration - (horizontalDuration / slides.length))
    );

    Here is a fork of your last example:

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

     

    Hopefully this helps.

    Happy Tweening!

  10. Hi,

     

    The problem is in the logic of your loop. Basically you're setting the start position of the elements and then moving them out of the visible section in one motion with no pause between them.

     

    This seems to work the way you intend:

    $verticalTextItems.each((index, item) => {
      gsap.set(item, {
        yPercent: index === 0 ? 0 : 100,
        opacity: index === 0 ? 1 : 0
      });
    
      if ($verticalTextItems[index + 1]) {
        timeline.to(item, {
          yPercent: -100,
          opacity: 0,
        }, "+=0.5")
          .to($verticalTextItems[index + 1], {
          yPercent: 0,
          opacity: 1,
        }, "<")
      }
    });

    Also there is no need to set the yPercent to anything above 100 in the set instance since you have absolute positioned elements. Pushing them beyond that point has no effect.

     

    Here is a fork of your codepen:

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

     

    Hopefully this helps.

    Happy Tweening!

  11. Hi,

     

    Your problem is here:

    gsap.set(gsap.utils.toArray(entry, name, desc, details), {
      clearProps: true
    });

    If you check the docs for the toArray method you'll see that the second parameter it's a scope element or selector:

    Parameters

    1. targets : [Object | String | NodeList | Array] - The target(s) that you want wrapped in a flattened Array (it can be selector text, objects, NodeList, jQuery objects, etc.)
    2. scope : [Element | Ref] (optional) - The Element (or React ref) to which the selector text scope should be limited, like calling .querySelectorAll([selector-text]) on this Element rather than the document. In other words, it will only return descendant Elements of the scope Element, like jQuery.find(). This is only helpful when targets is selector text.

    So basically you're telling GSAP, create an array with the entry element and use the name element as scope, the other two (desc and details) are just ignored, because the method is expecting two parameters.

     

    There are two solutions:

    1. Pass an array with the elements to the method:
      gsap.set(gsap.utils.toArray([entry, name, desc, details]), {
        clearProps: true
      });
    2. Don't use the toArray method. Those elements are already DOM nodes so you can wrap them in an array and be done with it:
      gsap.set([entry, name, desc, details], {
        clearProps: true
      });

    Hopefully this helps.

    Happy Tweening!

    • Like 3
  12. Hi,

     

    The ScrollTrigger Docs have a long section about the snap configuration options. Scroll to Usage & special properties, the properties are ordered alphabetically, so just scroll down to the S:
    https://greensock.com/docs/v3/Plugins/ScrollTrigger

     

    Here are a couple of examples of Horizontal Snapping:

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

     

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

     

    Hopefully this is enough to get you started.

    Happy Tweening!

  13. Hi @CptRobby and welcome to the GreenSock forums!

     

    This seems to be related to some stuff Vue does to refs that is a bit longer to explain here. But is definitely not a GSAP issue.

     

    If possible don't store your GSAP timeline in a ref, since that particular element becomes reactive as well, so if nothing in your app depends on that ref being updated, there is no actual need to store the GSAP Timeline in a ref, just create a variable and update that variable inside the GSAP Context instance:

    const block = ref();
    const button = ref();
    let tl;
    let ctx;
    
    onMounted(() => {
      ctx = gsap.context(() => {
        tl = gsap.timeline();
        tl.to(button.value, {
          opacity: 1,
          duration: 1,
        });
      }, block.value);
    });

    Finally is not a good idea to make paused: true a default in your Timeline configuration, since every single instance you add will be paused and you'd have to change that as you create them. Is better to just make the timeline paused:

    let ctx;
    let tl;
    
    onMounted(() => {
      ctx = gsap.context(() => {
        tl = gsap.timeline({
          paused: true,
        });
        tl.to(el, { /*...*/ });
        
        // Later in your code or in a method
        tl.play();
      }, scope);
    });

     

    Hopefully this helps.

    Happy Tweening!

    • Like 1
    • Thanks 1
  14. Hi,

     

    Be sure to have something like this in your .npmrc file:

    always-auth=true
    @gsap:registry=https://npm.greensock.com
    //npm.greensock.com/:_authToken=${NPM_TOKEN}

    This repo was deployed less than two weeks ago to vercel without any issues:

    https://github.com/rhernandog/gsap-bonus-npm-vercel

     

    Here is the live page (not a lot, just a proof of concept of a successful deployment and CI/CD pipeline):

    https://gsap-bonus-npm-vercel.vercel.app/

     

    If you inspect the console you'll find this line there:

    https://github.com/rhernandog/gsap-bonus-npm-vercel/blob/main/pages/_app.js#L8

     

    Hopefully this helps.

    Happy Tweening!

    • Like 2
    • Thanks 1
  15. Hi,

     

    This seems related to some SSR issue. Cassie's example is working fine on a React App but your example is not and here is an error I'm getting in the console:

    Warning: Extra attributes from the server: style
        at body

    That's in your layout file:

    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>

    If you remove that class from the body tag your example works as expected:

    https://stackblitz.com/edit/nextjs-jfi5ws

     

    Now, why Next is complaining about the style attribute on the body tag I have no idea 🤷‍♂️, as far as I can tell that should be valid HTML, unless I'm wrong about that.

     

    Hopefully this helps.

    Happy Tweening!

  16. Hi,

     

    Once again, without a minimal demo there is not a lot we can do. I don't know the difference between running a code with a use client or not. I'm not really aware of this particular flag. If you ask me React and Next have only complicated things even further in their latest versions with the server components, use client, app folder and such things. I worked on many Next projects when none of that nonsense was available and things were rendered in the server. Honestly it's like their looking for ways to complicate things as much as possible and make the lives of developers miserable 🤷‍♂️

     

    If you have a preloader what you could do is create a global context and trigger a state property in that context once that animation is complete. Then in your components have an effect hook that has that particular state property as it's only dependency and create your GSAP instances only when that property indicates that the preloader animation is complete. Something like this:

    useLayoutEffect(() => {
      if(!loaderComplete) return;
      const ctx = gsap.context(() => {}, scope);
      return () => ctx.revert();
    }, [loaderComplete]);

    Hopefully this helps.

    Happy Tweening!

  17. Hi,

     

    As far as I can see the codepen in Cassie's last post seems to work the way you expect. If you have other specs in your setup then be sure to include or them in your minimal demo in order to get a better idea of what you're trying to do.

     

    Maybe the issue could be your HTML and CSS more than a GSAP specific problem. First try to create an HTML/CSS setup that looks the way you intend, then start creating your animations without ScrollTrigger. Once your animations do exactly what you want, then add ScrollTrigger to the mix.

     

    Happy Tweening!

  18. Hi,

     

    Thanks for providing information about this.

     

    I'm not all that familiar with Svelte Transition yet, but normally other frameworks like Vue or packages like React Transition Group, have explicit in/out animation types for routes, so that allows you to move out a page before moving the other. Also they include ways to tell the Transition component/function that the animation is completed in order to, once the page transition is done, create your GSAP instances at a normal state soto speak and not midway the page transition animation.

     

    Happy Tweening!

×