Jump to content
Search Community

progress() not working with timeline lite and react

captainchemist test
Moderator Tag

Go to solution Solved by captainchemist,

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

Hi Everyone,

I am having an issue integrating React with Timeline lite. I can successfully play, pause, and rewind a timeline but when I try to read the current time using .progress() on the timeline object, I get undefined. I want that progress() function because I want to create a slider bar that will report the progress of the timeline and hopefully also allow for it to be adjusted.

 

My app is laid out like this, I have a container LectureLayout that has a draggableObjects container which itself has many draggable object components. I have a controls component which has the play, pause, reverse buttons. 

 

<LectureLayout>

   <DraggableObjects timeline={timeline}>

      <DraggableObject1>

      <DraggableObject2>

      <DraggableObject3>

   <Controls timeline={timeline}>

 

I create a TimelineLite() and pass it as a prop into the draggable objects and controls components. I load up all of the tween animations in the Draggable Objects container but since it is a prop of the LectureLayout container, I can read that successfully in the Controls component.

 

My main question is how can the .progress() function be returning undefined when I can use all of the other functions like play() and I can clearly see the timeline when I run a console.log

 

export class Controls extends React.Component {
  constructor(props, context) {
    super(props, context);
  }

  play(event){
    event.preventDefault();
    this.props.timeline.play();
  }

  componentDidMount(){
    console.log(this.props.timeline) //Successfully gives me a timeline object
    console.log(this.props.timeline.progress()); //This comes up as undefined
  }

  render(){
    console.log(this.props.timeline) //Successfully gives me a timeline object
    console.log(this.props.timeline.progress()); //This comes up as undefined
    
    return(
      <div>
        <Row>
            <ProgressBar/>
        </Row>
        <Row>
             <Button onClick={this.play.bind(this)} id="play">play()</Button>
        </Row>
      </div>
    )
  }
};

 

Link to comment
Share on other sites

Hi and welcome to the GreenSock forums.

 

Have you tried using an onUpdate callback in the timeline in order to log the progress?

var tl = new TimelineLite({onUpdate:updateLine, onUpdateParams:"{self}"});

function updateLine(target){
  console.log(target.progress());
}

Have you tried getting the progress after the component's update?. I've seen some issues with ajax data in react components returning props properties as empty in the componentDidMount method. Please try componentDidUpdate and the onUpdate callback to see if there's anything different.

 

Also please provide a codepen sample in order to get a better look at your issue:

http://greensock.com/forums/topic/9002-read-this-first-how-to-create-a-codepen-demo/

 

Here's a post on codepen's blog on how to set up a react app in codepen using babel and JSX:

https://blog.codepen.io/2015/05/19/babel-now-on-codepen-write-es6-javascript-and-react-jsx/

 

Finally, if you've been using GSAP with React, please take a little time to read this post and if you can give your opinion in the matter:

http://greensock.com/forums/topic/14639-greensock-reactjs-components-needed/

  • Like 1
Link to comment
Share on other sites

Thanks so much Rodrigo! I finally was able to replicate the issue in a Code Pen demo, check it out!

See the Pen RRLZgj?editors=0010 by anon (@anon) on CodePen

You can see that the demo plays just fine and I can restart it, but when I console.log the progress(), it gives undefined.

 

I tried playing around with your onUpdate callback and it definitely is on the right track because I start getting hundreds of console messages but they all give this error:

Uncaught TypeError: CreateListFromArrayLike called on non-object

Link to comment
Share on other sites

DO'H!!!!!!!!!!!!!!!

 

I'm terribly sorry, totally my bad  :oops:

 

The params in the onUpdateParams are passed as an array:

let tl = new TimelineLite({onUpdate:updateFn, onUpdateParams:["{self}"]});

Finally this code works on the main component of your codepen sample (thanks for setting it up!!):

class Application extends React.Component {
    constructor(props, context) {
    super(props, context);
  }
  
  tlUpdate(target){
    console.log(target.progress());
  }
  render() {
    var tl = new TimelineLite({onUpdate:this.tlUpdate, onUpdateParams:["{self}"]});
    return (
      <div>
        <Tweens tl={tl}/>
        <Controls tl={tl}/>
      </div>
    )
  }
}

Again, sorry for the incomplete code in the previous post.

 

Happy Tweening!!!

  • Like 3
Link to comment
Share on other sites

Thanks so much, this is beautiful! This totally outputs the correct progress in the console.log. Just as one followup question, how can I access that progress number in the Controls child component so that I can feed it into my slider? Do I have to setState() in the body of the tlUpdate function or am I able to somehow call the progress() in the <Controls> component? Thanks again!

Link to comment
Share on other sites

Hi,

 

What I would suggest is to set the progress of the timeline as a state of the parent component and in the onUpdate callback use setState() to update the value and pass the progress value to the child's props. Like that everytime the value of the parent state property is updated it'll update the child's props as well.

class Application extends React.Component {
    constructor(props, context) {
    super(props, context);
    // set up the initial state for the progress value.
    // by default the progress of a timeline is 0 at start
    this.state = {tlProgress:0};
  }
  
  tlUpdate(target){
    // update the state of the component
    this.setState({tlProgress:target.progress()});
    console.log(target.progress());
  }
  render() {
    var tl = new TimelineLite({onUpdate:this.tlUpdate, onUpdateParams:["{self}"]});
    return (
      <div>
        <Tweens tl={tl}/>
        <Controls tl={tl} tlProgress={this.state.tlProgress}/>
      </div>
    )
  }
}

Like that the controls component will have in it's props object the tlProgress property, which will be updated everytime the state of the parent component is updated. Like that you can use it to retrieve the progress value using componentDidUpdate:

class Controls extends React.Component {
  
  componentDidUpdate(){
    console.log(this.props.tlProgress);
  }
  
  render(){
    return (
      <div>
        <button id="restart" onClick={this.restart.bind(this, tl)}>restart</button>
      </div>
    )
  }
};

Hopefully this helps.

  • Like 2
Link to comment
Share on other sites

My thoughts were similar to yours but when I implemented it like you suggested, I get an error that "this.setState is not a function". The issue is also reflected in the codepen demo:

See the Pen XKzJay?editors=1111 by CaptainChemist (@CaptainChemist) on CodePen

 

I also tried adding .bind(this) to the this.tlUpdate function but that actually causes the browser to become unresponsive. Thanks again!

  • Like 1
Link to comment
Share on other sites

Mhh.. yep, this is basically a scope issue. GSAP callbacks have another option to set the scope of the callback, basically what this is referred to in the callback. Normally this refers to the GSAP instance (the timeline in this case) so you have to change it in order to refer to the component:

var tl = new TimelineLite({onUpdate:updateFn, onUpdateParams:["{self}"], onUpdateScope:this});

In the component's code this refers to the component itself, but as you already saw this makes the browser go bananas  :blink:. The problem is that every time the state is updated forces the component to be rendered again, which restart the timeline from zero (that's why the red box appears to be just a bit to the right of it's original position all the time), then after the first GSAP tick, the onUpdate fires, setting the state property, which renders again... and like that we're in an endless loop.

 

What you're need to solve is where you need the timeline to be defined, perhaps you should create the timeline in the nested component and pass the progress value to it's sibling via the parent. Unfortunately this has become mostly a React issue since the GSAP part is well covered. Check this, perhaps it could help:

 

http://stackoverflow.com/questions/34734301/passing-data-between-two-sibling-react-js-components

 

Basically what you're missing here is the C in the MVC structure of your app, something that can be used to share the data. React only accounts for the V . Perhaps you could see if Flux helps in solving the shortcoming:

 

https://facebook.github.io/flux/

 

Sorry that I can't help more than this.

Happy Tweening!!

  • Like 1
Link to comment
Share on other sites

  • Solution

As a final update, I was able to figure out how you can access the state of a timeline within react. The trick is to define the timeline in the parent component and then to add a onUpdate event callback within the child component. Here is a working example, I hope that it helps someone!

 

See the Pen XKzJay?editors=0010 by CaptainChemist (@CaptainChemist) on CodePen

  • Like 2
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...