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. Honestly I'd do my best to accommodate both instances to update the X value on the slides parent container and do some math in order to update the value from the handle Draggable instance in a way that reflects the drag amount of the handle.

     

    Another possibility is to create a regular TweenLite instance that should be updated via the handle's Draggable instance. For example use a regular Draggable with snapping for the slides container. At the same time create a regular TweenLite instance (this should remain paused all the time) that moves the slides container all the way to the left using the X value on it and update that TweenLite instance using Draggable, like this:

     

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

     

    That is, IMHO, basically the simplest way to achieve that.

     

    Happy Tweening!!

    • Like 3
  2. Hi Richard and welcome to the GreenSock forums.

     

    It seems that this could be solved by using the update() method all Draggable instances have, which is very useful when something outside the realm of the Draggable instance itself, updates it's target. On top of that right now you're updating two different things, that also could be a source of problems.

     

    Let's go backwards. First you're updating two different things. On the slides the Draggable instance is set to Scroll Left while the handle Draggable instance updates the slides X position. Those are two completely different things. My advice is to create a consistent update in both cases the handle callback and the property being updated by the Draggable instance that controls the slides.

     

    Second, if you're updating the same property that a Draggable instance is affecting, then the recommendation is to use the update method in order to tell that Draggable instance that the value of that specific property has been updated outside the Draggable instance. In that case GSAP updates the value in the Draggable instance and the next time the user drags that updated value will be reflected:

     

    https://greensock.com/docs/Utilities/Draggable/update()

     

    Unfortunately I don't have enough time to fiddle with your code and try to create a working solution, but hopefully this will help you get started.

     

    Happy Tweening!!

    • Like 5
  3. Hi Apollos, 

     

    I agree with Jack, there is a lot you want to do in your sample and basically most of the resources you need are here in the forums and other documentation. For example to draw an SVG path you can use the Draw SVG plugin. Also you could tween the width of a div element with no height but a border-top property as well.

     

    As far as using GSAP in React this basically comes to using refs in order to gain access to the DOM nodes and use them in a GSAP instance. Here is a introduction of how to get your React project working with GSAP with live-editable samples:

     

    https://greensock.com/react

     

    Happy Tweening!!

    • Like 2
  4. If you ask me it seems about right.

     

    Keep in mind that .set() instances are zero duration instances so, something that actually lasts nothing can have a progress? Is it at the start or the end?  So to me is actually correct that it doesn't have a progress.

     

    Of course the Yoda of GSAP, our beloved master @GreenSock will clarify this for us all why set tweens progress don't have :D

     

    Happy Tweening!!

  5. Hi and welcome to the GreenSock forums.

     

    The fist thing here is that when you create a ref in directly in a component, you get the component instance and not the DOM node or tree that the component ultimately renders:

     

    class MyApp extends Componetn {
      constructor(props) {
        super(props);
        
        this.myCard = null;
      }
      
      componentDidMount () {
        console.log(this.myCard); // returns a React component object
      }
      
      render () {
        return <div>
          <Card ref={card => myCard = card} />
        </div>;
      }
    }

     

    That because the ref callback is being called in a React Component instance and not a DOM node created in JSX.

     

    Then is not clear where you want to create the animations; In the parent component or on each card?.

     

    If you want to create the animations in the parent component then you should use Forwarding Refs in order to store a reference to the DOM node that sits at the top of the DOM tree created by that component. Forwarding refs is a bit of a complex resource for someone that is just staring with React, specially if your Card component can't be transformed into a functional component. If your Card component can be transformed into a functional component, then is pretty easy. This is an extremely simple example of using forward refs to create an animation with GSAP:

     

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

     

    An alternative is to use React Transition Group in order to create the animations inside each Card component. This approach can also be a bit convoluted as shown here:

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

     

    Finally if you want to access the DOM element directly inside each instance of the Card component without all the hassle of React Transition Group you're very close in fact. Right now you have this on your Card component:

     

    class Card extends Component {
      tl = new TimelineLite({ paused: true });
    
      componentDidMount() {
        console.log(this.refs);
      }
    
      render() {
        return (
          <div className="slide">
            <div className="card">
              <div className={`${this.props.card.id} card-img`} />
              <div className="card-content" ref>
                <p className="card-theme">{this.props.card.theme}</p>
                <h2 className="card-header">{this.props.card.header}</h2>
                <p className="card-para"> lorem lorem</p>
                <a className="card-link" href="#">
                  Read
                </a>
              </div>
            </div>
            <div className="prevnext">
              <button className="pn-btn" id="prev" />
              <button className="pn-btn" id="next" />
            </div>
          </div>
        );
      }
    }

     

    Keep in mind that the ref is a callback that, used as an attribute in the JSX code, grabs whatever is returned from the tag where is used, in this case a <div> element, but is a function, now you're only referencing that function but you're not doing anything with it. You have to create a reference to the DOM element in the constructor and then use the callback to update it at render time:

     

    class Card extends Component {
      constructor(props){
        super(props);
        this.tl = new TimelineLite({ paused: true });
        this.card = null;
      }
    
      componentDidMount() {
        console.log(this.card); // returns the div element
      }
    
      render() {
        return (
          <div className="slide">
            <div className="card">
              <div className={`${this.props.card.id} card-img`} />
              <div className="card-content" ref={div => this.card = div}>
                <p className="card-theme">{this.props.card.theme}</p>
                <h2 className="card-header">{this.props.card.header}</h2>
                <p className="card-para"> lorem lorem</p>
                <a className="card-link" href="#">
                  Read
                </a>
              </div>
            </div>
            <div className="prevnext">
              <button className="pn-btn" id="prev" />
              <button className="pn-btn" id="next" />
            </div>
          </div>
        );
      }
    }

     

    That particular use will allow you to use the <div> element in your GSAP instance with no problems.

     

    Hopefully this is enough to get you started.

     

    Happy Tweening!!

    • Like 1
  6.  

     

    Well, basically that has to do with dealing with asynchronous code. Since you're getting your app's data in the componentDidMount() event handler that code is executed and keeps waiting for an answer from the server and the rest of the code is executed regardless of that, hence the code is asynchronous. When you move your GSAP code to the .then() method of the promise returned by fetch() that is executed when the server sends a successful response, therefore you have the data and you can use it in your GSAP powered animations.

     

    Another alternative is to use async/await, but if you're not too familiar with promises it might come as a bit confusing. Basically async await allows you to write asynchronous code in a synchronous fashion, without the .then() and catch() methods.

     

    You can check this resources to learn about it:

     

    https://javascript.info/async-await

     

    And of course the crazy guy from Sweden (note, not every person in Sweden is crazy and maybe this guy isn't as well ;) ):

     

     

     

    Happy Tweening!!

     

    • Like 2
  7. My guess, besides what Shaun and Pedro already have pointed here, is that this is related to the fact that your menu component could be re-rendered every time you do a route change and that re-render causes new instances being added to the timeline controlling the menu farther and farther away in the timeline, as Shaun points. Just create your animation in the componendDidMount() method and use what Shaun suggested. Also check your app and see if it's actually necessary to re-render your menu component everytime a route changes, normally menus are added at the top of the app tree, because they don't mutate as the app's data or state  is being updated.

     

    @Shaun Gorneau and @Dipscom, the final tween in the timeline is not being added at an absolute time, the final number in that instance, stands for the stagger time between animations and not the position, that's why the stagger animation gets pushed every time.

     

    Happy tweening!!

    • Like 4
  8. Is basically the same issue as before. This:

     

    <Scroll ref={this.scroll} />

     

    Will create a reference to a React component instance, not a DOM node because the forwarded ref goes into another component:

     

    <ScrollSpy ref={ref}>
        <div className="one" />
        <div className="two" />
        <div className="three" />
    </ScrollSpy>

     

    This is basically whatever styled components (which is a tool that I have never used) returns. That most likely will be another React Component and not a DOM node. You could try to log <ScrollSpy> into the console and find out what that is. Your best choice is look up in the styled components API for a way to retrieve the DOM node created in order to access that and create the animation.

     

    Honestly I understand the usage of styled components by developers but personally I don't see any upside to it. Call me an old school guy, but I prefer my styles and my JS code in different files.

    • Like 4
  9. Hi,

     

    First of all the post editor has an option to include code snippets, is the button with the smaller and bigger than icon <>, it has syntax highlighting and auto-detects indentation. That makes reading code easier and faster, please do your best effort to use it.

     

    In the react aspect of your issue, yeah that's a pickle. React has the forward refs feature that allows to create a reference to the DOM node of a child component in it's parent, but in the case of an array of DOM nodes I'm pretty sure is not going to work.

     

    My advice would be to create the reference in the child component and store in the instance the array of DOM nodes. Then create a method to simply return that array:

     

    // Child component
    constructor (props) {
      super(props);
      this.navEls = [];
      this.getNavElements = this.getNavElements.bind(this);
    }
    
    getNavElements () {
      return this.navEls;
    }
    
    render () {
      return <nav>
        {array.map( (element, index) => <li ref={li => this.navEls[index] = li}></li>)}
      </nav>;
    }

     

    In the parent component use a regular createRef method to get a reference to the child component and execute the method that returns the array, then store it in the state:

     

    // parent component
    constructor (props) {
      super(props);
      this.myNav = React.createRef();
      this.state = {
        navElements: []
      };
    }
    
    componentDidMount () {
      this.setState({
        navElements: this.myNav.current.getNavElements()
      });
    }
    
    render () {
      return <div>
        <Navigation ref={this.myNav} />
      </div>;
    }

     

    Then you can use componentDidUpdate to create the animation or you can also do it directly in the componentDidMount method, is up to you actually.

     

    Here is a simple live sample:

     

    https://stackblitz.com/edit/react-gsap-staggerlist-in-child-component

     

    Happy Tweening!!!

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

     

    The problem in your app is that you're setting a ref in a component instance and not a DOM element in your JSX:

     

    <Menu visible={visible} menuRef={this.menuRef} />

     

    That particular reference will return a React Component instance and not a DOM node. In fact you should be seeing an error in your console, because a component instance doesn't have a current property, so this.menuRef.current should be undefined and GSAP should be complaining about it saying that it cannot tween a null target.

     

    The solution is not complicated at all. You have to create the animation logic inside your menu component and since you're already passing the visible state property as a prop to the Menu component you can listen for changes in the props in the menu component:

    // in the menu component
    componentDidMount () {
      this.menuTween = TweenMax.to(this.menuRef, 1, {
        autoAlpha: 1
      }).reverse();
    }
    
    componentDidUpdate (prevProps) {
      if ( prevProps.visible !== this.props.visible ) {
        this.menuTween.reversed(!this.props.visible);
      }
    }

     

    Here is an oversimplified example:

     

    https://stackblitz.com/edit/react-gsap-togglemenu-sample

     

    Happy Tweening!!

    • Like 3
  11. Mhhh... yeah the issue might be the fact that Gatsby's <layout> complains about some operation being done by SplitText. Unfortunately in Codesandbox, using the Gatsby set up for a new sample, has some issues with adding SplitText using the S3 link for live samples. A simple React setup using CRA works as expected:

     

    https://codesandbox.io/s/8kprw408yl

     

    Anyway, you could try using React.fragment around your tags and see if it works.

     

    Happy tweening!!

     

    • Like 1
  12. 15 hours ago, GreenSock said:

    That is indeed due to very small rounding errors that plague certain math operations in binary systems.

     

    @PointC See?? the binary system is at fault here, so you should blame the Egyptians, Indians, Chinese, Leibniz (I think that's the way is written) and the geniuses that thought it was a good idea to use it on IT. We're all victims here :D

    • Haha 3
  13. Works like a charm Jack, thanks a bunch!!!

     

    Yeah, since the values are reaching less than zero and almos 130, basically I added a return at the top of the callback if the value for x is less than 0, like that the extra conditional logic is not expensive at all. Finally PIXI takes care of rounding the value of y to 130, so nothing too complicated.

    • Like 3
  14. Yes, in fact that particular effect is done using PIXI JS.

     

    Lucky for us @OSUblake, one of the superstars we have around here is quite a PIXI/GSAP and anythign you throw at Him expert, so He has you covered regarding samples.

    The title says everything here:

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

     

    Here is something that looks similar but is achieved in a different way:

    See the Pen QVozey?editors=0010 by osublake (@osublake) on CodePen

     

    Finally:

    See the Pen qmXPMm?editors=0010 by osublake (@osublake) on CodePen

     

    As you can see this is done using PIXI's displacement filter which can be used with GSAP and the PIXI Plugin.

     

    Hopefully these will help you get started.

     

    Happy Tweening!!

    • Like 3
  15. I don't know if someone else has stumped into something like this before. I did a quick search and couldn't find anything.

     

    As you can see in the pen, I'm updating the values of an object using the bezier plugin with the idea of drawing a rounded rectangle in PIXI using quadratic bezier because it has just one control point which keeps code simpler. The issue comes at the end of the GSAP instance, the final value should be { x: 0, y: 130 }, but the values returned by the bezier plugin are smaller than that, I mean reaaaaaally small, the problem is caused in the 15th decimal position!!.

     

    I have no issue in adding some extra logic in my code, in order to force the values to 0 and 130, but since I'm not extremely familiar with the bezier equations used by GSAP, I don't know if this is by design, some sort of inevitable tradeoff of the calculations inside the plugin or an actual issue.

     

    Happy Tweening!!

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

  16. Hi and welcome to the GreenSock forums.

     

    You're using PIXI 2.2.5?, that's a bit old and there is a good chance the plugin doesn't work with filters in that particular version of PIXI. If possible please try to update to the latest 4.x version of PIXI. Unfortunately I don't have time to go through the difference in filters implementations in these different versions of PIXI (2.2.5 vs 4.8.6) and see where exactly is the problem. But my guess is that perhaps it has changed during that time.

     

    Perhaps our great master @GreenSock can shed some lights in this matter.

     

    Happy tweening!!

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

     

    You got really close, in fact you even created a boolean to check when the data fetching is completed. All you have to do is compare that part of the state in the componentDidUpdate() method in order to trigger the animation, just change your component to this and it should work as expected:

     

    componentDidMount() {
      this.fetchUsers();
    }
    
    componentDidUpdate (preProps, preState) {
      if ( preState.isFetching && preState.isFetching !== this.state.isFetching ) {
        TweenMax.staggerFrom('#list-1 li, #list-2 li, #list-3 li', .5, {
          x: -10,
          autoAlpha: 0
        }, .2);
      }
    }

     

    Basically this compares the the isFetching property of the state before and after every update. Since the only case where the previous value is true and the next value is false, is after getting the data from the API, you can trigger the animation there.

     

    Happy Tweening!!

    • Like 5
  18. Mhhh... I'm terribly sorry I misunderstood the issue you're having. I rushed away to think this was a tree shaking issue where the animation wasn't happening at all and now I see is about the animation running differently in development and production. Apologies all around :oops:

     

    Have you tried using just rotation instead of rotationZ, the effect is the same. Also GSAP interprets numbers as pixels by default, I see some strings in your config objects, perhaps try passing just numbers:

     

    this.menuToggle
      .set(this.controlit, {className:"-=closemenu"})
      .set(this.controlit, {className:"+=openmenu"})
      .to(' .top', .2, {y:-9, transformOrigin: '50% 50%'}, 'burg')
      .to(' .bot', .2, {y:9, transformOrigin: '50% 50%'}, 'burg')
      .to(' .mid', .2, {scale:0.1, transformOrigin: '50% 50%'}, 'burg')
      .add('rotate')
      .to(' .top', .2, {y:5}, 'rotate')
      .to(' .bot', .2, {y:-5}, 'rotate')
      .to(' .top', .2, {rotation:45, transformOrigin: '50% 50%'}, 'rotate')
      .to(' .bot', .2, {rotation:-45, transformOrigin: '50% 50%'}, 'rotate');

     

    That does exactly the same animation, give it a try and let us know.

     

    Finally, just in case tree shaking is causing this, that is the actual import statement and the GSAP tools you're using in your file?

     

    Happy Tweening!!

    • Thanks 1
  19. In order to get the instance of the <li> element in the DOM you can use refs. If that's not what you're looking for, please be more specific about it.

     

    Regarding the second part I'm a bit lost. The first part is that you want to add an extra animation depending on some specific criteria for a specific card. You can use the same array.map() to store some data in an array and then return the array of JSX elements. Keep in mind that the callback inside a .map() method has to return some element that is added to an array, but before that you can run any logic you want. Then you can use a for or for each loop to create your animations and check there if the extra instance has to be created using either the original data or a new array you can create in the .map() method. Is also important to remember that the .map() method goes through an array in ascending order, that is starts with the element at index 0. Because of that, the order in which the DOM elements are added to the array that will hold their references created in React, is the same the data for each element in the original array, so you can easily use a forEach and use the index value in the callback to read the original data and avoid adding more attributes to the DOM element.

     

    // original data
    const originalData = [];
    
    //in the component constructor
    constructor (props) {
      super(props);
      this.animateCards = this.animateCards.bind(this);
      this.cards = [];
    }
    
    // in the render method
    render () {
      <div>
        {originalData.map (card, index) => (<li ref={e => this.cards[index] = e}>Element</li>)}
      </div>
    }
    
    // finally when creating the animations
    animateCards () {
      this.cards.forEach((card, index) => {
        // here this.cards[index] matches the data in originalData[index]
        // you can use originalData[index] to deicde if an extra animation is needed
      });
    }

     

    Finally if you want to add an extra DOM element depending on the data, you should do that in the array.map() inside the render method and not somewhere else. Also base that on the original data and not some attribute passed to the rendered DOM element that results from the .map() method.

     

    Please do your best to provide some small and simplified sample code in order to get a better idea of what you're trying to do.

     

    Happy Tweening!!

    • Like 2
  20. Well as far as I know there is no direct way to get the type from an instance, but it could be something I'm not aware of, Jack could offer a final say in this.

     

    What I can think of is the following. Single instances like TweenLite/Max don't have the .add() method while Timeline instances do, so that tells you if you're dealing with a timeline or single tween.

     

    A TweenMax instance has the yoyo property so you can check that and finally a TimelineMax has the .getLabelsArray() method, as you already know :D, so a way to check would be this:

     

    var checkInstance = function (instance) {
      if ( instance.add ) {
        // instance is a timeline
        if ( instance.getLabelsArray ) {
          // instance is TimelineMax
        } else {
          // instance is TimelineLite
        }
      } else {
        // instance is TweenLite or TweenMax
        if (instance.yoyo) {
          // instance is TweenMax
        } else {
          // instance is TweenLite
        }
      }
    };

     

    Happy Tweening!!

    • Like 1
  21. Thanks Buddy!!! I learned from the best ;)

     

    The one option I can think, by looking at the GSAP code is extend TimelineLite prototype.

     

    Since get labels array is quite simple:

     

    https://github.com/greensock/GreenSock-JS/blob/master/src/uncompressed/TimelineMax.js#L431

     

    And TimelineLite has a labels object just like it's Max counterpart:

     

    https://github.com/greensock/GreenSock-JS/blob/master/src/uncompressed/TimelineLite.js#L25

     

    This shouldn't return any problem (running some simple tests doesn't create an issue):

     

    TimelineLite.prototype.getLabelsArray = TimelineMax.prototype.getLabelsArray;

     

    Unless there is something I'm not totally aware of, in which case our beloved leader and tween-guide spirit @GreenSock could warn us against this.

     

    Although this doesn't actually resolves anything as far as the original question, because still there is no way to introduce more than two labels in the main TimelineMax instance that holds the stagger methods. Unless Jack comes up with a new element in the API that allows dynamic labels in stagger methods using a base name for them and switching to TimelineMax. But IMHO it doesn't sound too useful, I'd go with the loop in order to add the animations to a TimelineMax instance.

×