Jump to content
GreenSock

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

Rodrigo last won the day on March 17 2019

Rodrigo had the most liked content!

Rodrigo

Moderators
  • Posts

    1,768
  • Joined

  • Last visited

  • Days Won

    158

Posts posted by Rodrigo

  1. Hi,

     

    As mentioned before I haven't had time to dig deeply into the Hooks API and all I can offer right now is a simple example but no real explanation on how it actually works, sorry about that.

     

    Here is a live sample using a Timeline instance with React hooks:

     

    https://stackblitz.com/edit/gsap-react-hooks-timeline-instance-toggle

     

    As you can see it uses a useEffect at first run in order to create the timeline and then you can add all the child instances you want to the timeline.

     

    Unfortunately this doesn't works:

     

    let tweenTarget = null;
    const tl = new TimelineLite({paused: true});
    
    useEffect(() => {
      tl
        .to(tweenTarget, 0.5, {x: 100})
        .to(tweenTarget, 0.5, {y: 100})
        .to(tweenTarget, 1, {rotation: 360})
        .reverse();
    }, []);
    
    useEffect(() => {
      tl.reversed(!toggle);
    }, [toggle]);

    Even though in the useEffect callback triggered by the toggle update, tl is defined and it's reversed state is updated, nothing happens. This is the part I can't give you a proper explanation about.

     

    For the foreseeable future just work with the timeline in the initial useState and then in the useEffect add the instances, like in the live sample. As soon as I have an official explanation about this, I'll post it here and in the other threads dealing with hooks.

     

    Happy Tweening!!!

    • Like 4
  2. @SENPAI & @xraymutation,

     

    This is trickier than one can expect because instead of using RTG to animate the transition between route components, the idea is to animate in a full page overlay, change the rendered component based on the new route and then animate the overlay out. Normally in RTG you pass the component that matches the target route which is animated.

     

    One solution I can think of is to play with the entering and exiting API's from RTG in order to animate just the overlay and produce the route change using a set instance in the addEndListener in order to have everything working with GSAP's timing. Of course this is easier said than done. Hopefully during the weekend I'll have some time to check this, but I can't make any promises since I'm extremely tight right now time-wise.

     

    An alternative is to create an animation in the overlay and use react router's API to make the route changes using GSAP callbacks. The trade-off in this approach is that you loose the capacity of animating the route changes using the browser's forward and back buttons, in those cases the routes change without any animation.

     

    Let me know which approach you prefer and perhaps we can work out something together.

     

    Happy Tweening!!!

    • Like 3
  3. @danboyle8637 I made a scroll spy for a React app a while ago based on this repo: https://github.com/makotot/react-scrollspy. It is quite experimental and rudimentary, If you want I can look around to find it and give it to you so you can check it. The principle is quite simple, the code listens to the scroll event (I know is not the best way) of a main wrapper and compares the scroll top value with the position of the elements that you want to animate or in the use case of that particular project set the active menu item. When the scroll position exceeds the top position of an element and if the current element is different from the value stored in the state, it updates a visible property in the state. Like that the state is updated only once per element and not on every scroll update. Is not the most efficient way but is far more efficient than constantly updating the state on every scroll event.

    • Like 1
  4. Honestly I'm out of my element with the intersection observer API here so I won't be able to help you with that, sorry :blink:

     

    The one thing that gets my attention is the fact that the animation's target is the same element. If we're not talking about a dynamic element why not create the animation in the same component and trigger it with the intersection observer (if that is even possible).

     

    By playing a bit with your code and adding some callbacks I can see that the animation is actually starting, updating and completing, so the issue lays on the DOM node passed to it not being affected by the GSAP instance which is odd.

     

    Unfortunately the only advice I can give you is try to create your animation inside the component and just control that instance instead of creating it over and over again and use that in the component's hook.

    • Like 3
  5. Honestly I haven't played with the intersection observer API at all. By doing some quick reading on the subject I don't see any issue as long as you're careful about passing an actual DOM node to the function that creates the GSAP instance. IMHO I don't see any big issue on keeping the code that creates the GSAP instances inside the components unless is exactly the same code for a bunch of different components, and I mean that in the DRY-way (Don't Repeat Yourself), ie, if you don't want to write the same code over and over again.

     

    Regarding the kill feature, this is not like a biological kill in terms that the GSAP instance ceases to exist, basically the instance is stopped and you can be sure that is not moving in any direction and that anything related to it will be executed until is explicitly set in that way. If you want to completely remove the instance you can reset it's reference in memory with null. The only check I can think of is the isActive() method, that should return false if the GSAP instance was killed. Finally the GSAP instance should be killed even though is instantiated in a different file because the DOM node will have a reference to the instance's position in memory, this does not depends of the context where the animation is actually created, which is exclusive of the code block where it happens. Also keep in mind that this will eventually happen in the same file but in a specific closure of the bundled file between lines X and Y.

     

    Happy Tweening!!

    • Like 2
  6. Hi and welcome to the GreenSock forums.

     

    Sorry to hear that you're experiencing problems with GSAP. Unfortunately it seems to be an issue with some CORS issue accessing bundlephobia.com's API, so the live sample is not working, for me at least.

     

    In this post is discussed how to create and toggle animations using the Hooks API:

     

     

    Also you can check a live simple example here as well:

     

    https://stackblitz.com/edit/gsap-react-hooks-toggle

     

    For I can see in your code you're using two different timelines to affect the same elements into different values. Ideally try to create a GSAP instance that you can toggle for each sub-menu element. Also you're adding new instances to the timeline every time the component is updated, so try to add an empty array when creating the GSAP instances so that code runs only when the component is mounted and toggle the animations using another useEffect call only when the open state property is updated.

     

    Hopefully this can help you as you try to get a working live sample so we can take a better look at it.

     

    Happy tweening!!

    • Like 2
  7. Hi, you can create the split text as a property in the component's class, in the constructor:

     

    constructor (props) {
      super(props);
      
      this.title = null;
      this.titleChars = null;
    }

    Here this.title should point to a ref in the render method

     

    Then in the component did mount method you can create the SplitText instance and assign the chars to the property created in the constructor:

     

    componentDidMount () {
      const mySplit = new SplitText(this.title, {
        type: "words, chars",
        position: "absolute"
      });
      this.titleChars = mySplit.chars; // array of DOM elements (<div> tags)
      
      TweenMax.staggerTo(this.titleChars, 1, {x: 10, y:-10, rotation: 180, autoAlpha: 0}, 0.1);
    }

     

    That is working for me, give it a try and let us know how it works.

     

    Happy tweening!!

    • Like 4
  8. You could filter the visible elements and render just those using RTG, like in this sample:

     

    https://stackblitz.com/edit/gsap-react-simple-transition-group?file=simple-transition.js

     

    Of course that is just one element but is the same principle. When the show value in the state is toggled the element is animated in/out with the consequent mount/unmount.  Instead of managing the show value on each component's you could pass it as a prop to each element.

     

    If you ask me I would use array.filter() to get a filtered version of the array elements that should be visible and render just those:

     

    https://stackblitz.com/edit/gsap-react-transition-group-list

     

    In that sample as soon as you remove an element, the array is reduced but the element is still rendered. As the animation-out is completed the element is removed from the App's tree. Take a look at both samples and hopefully you'll get a good grasp on how to tackle your situation.

     

    Finally you could take a look at this article by Addy Osmani:

     

    https://addyosmani.com/blog/react-window/

     

    Happy Tweening!!

    • Like 2
  9. You're welcome.

     

    For styles I'm an old fashioned guy and use SASS in separate files. I just don't like styled components. There used to be this concept called "Separations of Concerns" which I still follow to this day. IMHO styles go with styles, pure JS goes with pure JS and React code goes with React code, but again that's just my and my two cents on the subject. If you use breakpoints for animations my advice would be to use the context API and the root component of your app to keep track of the screen size. Like that you can check the screen size when creating/running your animations on every component in the app without the need of passing props down the component tree. Of course if at some point you plan to implement redux you can use it as well to spread the screen size from a single component that listens to the window resize event.

     

    The jumpy behaviour could be related to sub-pixel rendering but if you're using x and y that shouldn't be an issue. Perhaps the content your animating is creating the issue, maybe large images with transparency or shadows. Those things normally cause a lot of stress on the rendering engine. This is just on safari? Chrome, Firefox, Edge work OK?

     

    Happy Tweening!!

    • Like 3
  10. Hi and welcome to the GreenSock forums.

     

    Honestly I don't think using transition group is a good fit for the type of site you have in mind. Transition group fits better in cases you need to animate elements that will be mounted/unmounted in the React app, rather than regular animations.

     

    The best approach I can think of is to use either React's context API, redux/mobx or direct props to pass a method to all child components to add the particular timeline of each component to the master one. I would avoid doing this at all cost:

    // create react class
    class Box1 extends Component {}
    
    export default Box1;
    
    export const Timeline1 = () => {};

     

    Mainly because there is no guarantee that the DOM element will be mounted when the file actually reaches the point of the function definition that creates and returns the timeline. Instead create the timeline inside the component (the react-way soto speak) and then use the method created in the parent component to send that animation and add it to the master timeline. Something like this:

     

    Parent Element

    class App extends Component {
      constructor (props) {
        super(props);
        this.masterTl =  new TimelineMax({useFrames:true, paused:true});
        this.addChildTimeline = this.addChildTimeline.bind(this);
      }
      // method to add child timelines to the master
      addChildTimeline (childTl, position) {
        this.masterTl.add(childTl, position);
      }
      render() {
        return <div>
          <Box1 addChild={this.addChildTimeline} />
        </div>;
    }

     

    Child Component

    class Box1 extends Component {
      constructor (props) {
        super(props);
        this.box1 = null;
        this.tl = new TimelineLite({useFrames:true, paused:false});
      }
      
      componentDidMount() {
        this.tl
          .to(this.box1, 200, {
            opacity: 1,
          })
          // here you'll have to find a way to plug this duration value
          .to('.box-1', duration*2, {
            scale: 2.5,
            top: "80%",
            left: '-30%',
          }, "-=200");
        this.props.addChild(this.tl, "+=200");
      }
      
      render () {
        return <div>
          <div ref={div => this.box1 = div}>
            // box1 content here
          </div>
        </div>;
    }

     

    As you can see transition group is not needed at all and for this setup it will only make things more complicated.

     

    Unfortunately I don't have time right now and for the foreseeable future to create a concrete sample of this, so I hope that this simple advice is enough to get you started.

     

    Happy Tweening!!

    • Like 4
    • Thanks 1
  11. Hi and welcome to the GreenSock forums.

     

    Yeah  this is a bit more complicated since you're using SVG and regular DOM nodes. In this sample I'm using just DOM nodes so everything works in the intended way:

     

    See the Pen Ehjbd by rhernando (@rhernando) on CodePen

     

    Perhaps you could use a trigger or remove the <div> tag from the SVG altogether and use some other way to position it. I'm not an SVG expert and is the first time I see the <foreignObject> tag actually and my best guess is that using that approach could be the cause of your issues, so beyond what's in my sample and that particular advice I don't know if I can do much more.

     

    Happy Tweening!!!

    • Like 3
  12. Hi and welcome to the GreenSock forums.

     

    Unfortunately I don't have a lot of time to check the details of your demo and see what could be causing the issue.

     

    As far as passing a Component Ref down as prop, those are objects and there is definitely nothing against passing objects as props, since you're not actually passing the object but the reference, but is not something I would do. Instead of passing the result of the Ref callback (as that suspicious article you mention says ;)) I would try to use Forwarding Refs in order to get the DOM elements of each element you want to make draggable in the parent and create the draggable instances there, where you have direct access to the reference of the actual DOM node that would act as the bounds. Since you're using functional components it should be fairly easy. In this sample instead of creating a TweenLite instance you would create a Draggable one:

     

    https://stackblitz.com/edit/react-forwarding-refs-gsap

     

    Another alternative is to take advantage of the fact that bounds can be passed as an object, so you can use the dimensions of the parent component and pass it down as a prop to the child component and use that rectangle to enforce such bounds using positioned elements, offsetTop and offsetLeft to calculate the bounds when the Draggable instance is created.

     

    As you can see there are a couple of alternatives that I can think right of the top of my head. i'm pretty sure that other solutions are possible and that hopefully these will help you getting started.

     

    Happy Tweening!!

    • Like 3
    • Thanks 1
  13. Well I was creating a sample an Jack beat me to it.

     

    Also a piece on friendly advice. Try to avoid updating the state of any component on events that are triggered a lot like mouse move or scroll. It could create quite a burden in the app since that component re-renders and so it will any child component in it even if they don't re-render because of the state update, Reat still will look and do a shallow comparison to see if something has to be updated. If possible try to set up an event emitter and store the mouse position outside the class, remember that you're working with ES6 modules, which means that you can export the event emitter from the main file (or other file if you want), create the event listener in the new cursor file and emit the event in the parent component, that will be far cheaper in terms of processing than updating the state on every mouse move.

     

    If you take a look at this scroll spy library, you'll see that the guy does and stores all the calculations in an object and then compares those values with the ones stored. Once the scroll value exceeds the value of one of those items the state is updated only once:

     

    https://github.com/makotot/react-scrollspy/blob/master/src/js/lib/scrollspy.js#L191-L201

     

    Happy Tweening!!!

    • Like 3
  14. Hi,

     

    I haven't used hooks a lot, since I've been extremely busy with literally no free time so I couldn't tell you in this one. The one solution (that you probably are aware of) I can suggest is to use a regular class component just for the switch element. I know is not the ideal case, but with a component that small the overhead created by the extra code shouldn't be noticeable, unless we're talking about hundreds of instances.

     

    You can compare the outcome of this small snippet in Babel's repl playground:

     

    const MyComponent = () => {
      const tl = "TimelineInstance";
      return <div></div>;
    }
    
    class ClassComponent extends Component {
    	constructor(props){
        	super(props);
        }
      
      render() {
        return <div></div>;
      }
    }

     

    Of course Babel is going to add a few lines at the start of the outcome for the class declaration but you'll find this:

     

    var MyComponent = function MyComponent() {
      var tl = "TimelineInstance";
      return React.createElement("div", null);
    };
    
    var ClassComponent =
    /*#__PURE__*/
    function (_Component) {
      _inherits(ClassComponent, _Component);
    
      function ClassComponent(props) {
        _classCallCheck(this, ClassComponent);
    
        return _possibleConstructorReturn(this, _getPrototypeOf(ClassComponent).call(this, props));
      }
    
      _createClass(ClassComponent, [{
        key: "render",
        value: function render() {
          return React.createElement("div", null);
        }
      }]);
    
      return ClassComponent;
    }(Component);

     

    As you can see the difference is not much, for something as simple as this of course, but as I mentioned, the gain in a small component that it doesn't have hundreds of instances the overhead is minimal and most likely unnoticeable.

     

    3 hours ago, danboyle8637 said:

    I don't know if I can do that in my site because with the tl outside the function... i think it might be global and then conflict with other tl's I've created... but then again since those are in functions themselves... maybe not. 

     

    Actually there is not a lot of issue in creating instances outside the declaration of a React component. Keep in mind that those are just object constructors, nothing more. Also is very important to keep in mind that the bundling tools (today almost everyone uses Webpack) creates independent, encapsulated modules so each file has no communication with the global scope unless is set up in such way, which @PointC's suggestion doesn't so it should be very safe to do so.

     

    Hopefully this comes as helpful.

     

    Happy Tweening!!

    • Like 2
  15. Hi and welcome to the GreenSock forums.

     

    The CDN links are working for me. Perhaps the issue is the order the tags are placed. Remember to put the tag with the href to your code after the tags with the CDN links.

     

    If your code is in a file called myScript.js, then you should add the tag for that after the GSAP files and for that matter after every other library or framework you use in your site:

     

    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.2/TweenMax.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.4.0.min.js"></script>
    <!-- NOW ADD YOUR FILE -->
    <script src="js/myScript.js"></script>

     

    Happy Tweening!!!!

    • Like 4
  16. Well, I won't say that every jQuery method returns the jQuery instance, because I could be wrong, but is for chaining purposes. In this case you're basically selecting the slider element and assigning a width based on the calculations you made previously on your code.

     

    What's odd is that you didn't thought about doing the same you're already doing with the handle, that is use a set() instance to set the width of the slider element:

     

    TweenLite.set(slider, { width: boxWidth * sections });

     

    That seems to work OK in the codepen sample.

     

    This hopefully will help you get a better understanding of the different widths and heights of an HTML element object:

     

    https://stackoverflow.com/questions/21064101/understanding-offsetwidth-clientwidth-scrollwidth-and-height-respectively

     

    Happy Tweening!!!

    • Like 4
×