Jump to content
Search Community

second timeline not getting triggered after the first one finish

tomsah test
Moderator Tag

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

Hello GSAP community,

 

I am trying to play 2 timelines one after the other.

when timeline 1 finishes, immediately play timeline 2.

 

After lots of trying and reading the forum, I still haven't found a solution yet.

My problem is when I get some panels open and I click on the menu button, I want the open panels to slide back then immediately animating all the panel to go offscreen

 

So far I need to click twice on the menu button for this to happen

I am using a master timeline, as i understand is the way to do such things.

 

what do I do wrong in the master timeline? why are the timelines not chaining?


Sorry the code is a bit messy but I’ll work on refactoring soon, however, any tips will be welcome

Thanks a lot

See the Pen jzzMde?editors=0011 by tomsah (@tomsah) on CodePen

Link to comment
Share on other sites

I don't know how you have set up things, that's a lot of code to go through. Maybe you are setting some flag that determines if panels are open or closed. (Never used react either)

 

Following is simple demo, how I would do it. Keep track if panels are hidden or not and animate them to the hidden or visible state. Right now in your example, I am guessing that when you click on any panel you are changing the state so when you click your menu button it just resets their position instead of hiding them. Hope this gives you idea.

 

See the Pen jzzLRe by Sahil89 (@Sahil89) on CodePen

 

  • Like 4
Link to comment
Share on other sites

 

Hello and thank you for your answer.
 

First sorry for my long answer and my bad first explanation, Let me try to explain it better,


The way the panels are moving at the moment is exactly the way I want them to and my issue is at a very particular time of the animation.


I am using 2 timelines,
One (menuTl) is used to bring in and out of the viewport all the panels at once and it is trigger by the menu button. (works fine).

a second one (previewCaseTl) which animate the panel individually (the way it works at the moment) depending on which one is clicked, while the menu is open. This is trigger by the panel on click.

But If I got some open panels and I click on the menu button, I would like that the second timeline closes the open panels and immediately after that the first timeline trigger and brings the all set of panels off-screen.

To try to achieve that, I was trying to use a master timeline (closeAllTl) to chain my 2 timelines.

This is where it does not work exactly as I want and I don’t understand why or how to fix it.

What do I do wrong in the 'closeAll' function when I add the 2 timelines to the master one?

At the moment I need to click twice on the button menu for it to happen.

 

I am sorry, I am still not sure that I am explaining myself correctly but to clarify here is the steps to reproduce

 

1 click on the menu button, all the panels are coming in

2 click on panel 3, panel 1, 2 and 3 are sliding,

3 click on the menu button only panel 1, 2, 3 slide back

4 click on the menu button again all the panels are sliding off screen.

 

I would like that step 3 and 4 happen only with one click, so panel 1, 2, 3 slides back first then all the panels are sliding off screen.

That’s why I tried to use a master timeline for this particular case.
Please see comment in the closeAll function

 

Thanks again

Link to comment
Share on other sites

Something is wrong with your previewCase function, it throws errors as follow,

 

uncaught exception: Cannot add undefined into the timeline; it is not a tween, timeline, function, or string.
react-dom.development.js:619:7
uncaught exception: Cannot add undefined into the timeline; it is not a tween, timeline, function, or string.

 

  • Like 3
  • Thanks 1
Link to comment
Share on other sites

Sorry for the delayed answer but I got busy and has a little time to look at this issue lately.
Anyway back to it.
The error that @Sahil   spotted is because I am passing the function This.previewCase() into my master Timeline using the add method.

I do not understand why I cannot do that, as the error message seems to say that we can pass function.

I really need help in that one, I have been trying for a while now and get nowhere.

How can I trigger a function inside an add() method? 

 

Link to comment
Share on other sites

Well I see two problems,

 

1. You are using this.previewCase() with parenthesis so it executes function and returns undefined to add method because your function doesn't return anything.

2. Your function takes id as parameter. If I understand correctly, it takes id of single element and animates it. So in this case you are passing nothing to it. So you want to animate all elements you will need another function that does it. Makes sense?

 

I have never used react plus that's a lot of code to go through. @Rodrigo Might be able to help you but he previously has spent a lot of time helping you. I would suggest you to start small before starting to build something too complex. If you are just getting started with GSAP and/or React, I will also suggest to just create your effect in plain JavaScript before implementing it with React.

  • Like 5
  • Thanks 1
Link to comment
Share on other sites

Hi @tomsah,

 

Unfortunately I got caught up in a project now and haven't been able to follow up on your slide menu project. I'll try to whip something tomorrow to try to help you. For the moment what you should do is add to your menu component a property in the state in order to know if any panel is expanded or not and if the menu is being collapsed, ie, the user wants to hide the menu. The idea is this: when the menu button is clicked check that state and if a panel is open reverse the panels animation, then using a onReverseComplete instance check if the idea is to hide the menu or just collapse the panels. If the idea is to hide the menu then reverse the menu animation, if not do nothing.

 

Happy Tweening!!!

  • Like 3
  • Thanks 1
Link to comment
Share on other sites

Hi,

 

This seems to do what you're trying to achieve or what I understood at least.

 

See the Pen JLevjG?editors=0010 by rhernando (@rhernando) on CodePen

 

I'll go in as much detail as possible.

 

First I got rid of almost all your code, nothing against your code, is just my unwillingness to go through a lot of code I didn't write, normally is faster to just rewrite things.

 

I went as much as possible in the react way of doing things. So I removed the global scope timeline for the menu and added it to the component, like this:
 

class App extends React.Component {
  constructor(props) {
    super(props);
    
    this.state = {
      menuExpanded: false,
      targetPanel: null
    };
    
    // menu items
    this.menuItems = [];
    
    // menu tween
    this.menuTl = new TimelineLite({paused:true});
  }

  componentDidMount() {
    this.createMenuTween();
  }

  // bring the project to view//
  createMenuTween = () => {
    this.menuTl
      .staggerTo(this.menuItems, 1, {
        cycle: { x: ["56%","64%", "72%", "80%", "88%"] },
        ease: Power2.easeInOut
      })
      .reverse();
  };

  render() {
    return (
      <div className="main">
        <button onClick={this.toggleMenu} id='toggleMenu'> menu </button>
        <div className="projects" ref="projects">
          {Data.projects.map((project, index) => {
            return (
              <div
                className="project"
                ref={ project => this.menuItems.push(project)}
                key={index}
                id={`project-${index + 1}`}
                onClick={this.togglePreviewCase.bind(null, index)}
              >
                <span> {project.name} </span>
              </div>
            );
          })}
        </div>
      </div>
    );
  }
}

 This is the basic setup to render the DOM elements. As you can see I attached two properties to the component, the menu timeline and a menu items array:

// menu items
this.menuItems = [];
    
// menu tween
this.menuTl = new TimelineLite({paused:true});

Also I added the menu items to the array using the ref method on each menu item:

<div
  className="project"
  ref={ project => this.menuItems.push(project)}
  key={index}
  id={`project-${index + 1}`}
  onClick={this.togglePreviewCase.bind(null, index)}
>
  <span> {project.name} </span>
</div>

The idea behind using an array is that GSAP can use them without any problems, weather is to create a stagger animation or a common animation for all the elements. Since you're using the cycle property for each element there's no need to use a stagger method, just a regular to() instance that animates all the elements at the same time. The rest is pretty much self explanatory and we've been through that in a different topic. The main idea, as mentioned in my previous post, was adding a property in the state to track if the menu is expanded and if there's a menu panel expanded as well (menuExpanded, targetPanel).

 

Then is the toggle method, we'll go into the basic stuff of this and then get back to it:

  toggleMenu = e => {
    const { menuExpanded, targetPanel } = this.state;
    // if a panel is expanded, collapse any panel and then reverse
    // the menu timeline
    if ( targetPanel !== null ) {
      return TweenLite.to(
        this.menuItems.slice(0, targetPanel + 1), 1, {
          x: '+=50%', ease: Power2.easeInOut, onComplete: () =>{
            // toggle the state property
            this.setState({
              menuExpanded: !menuExpanded,
              targetPanel: null
            });
            this.menuTl.reversed( menuExpanded );
          }// on complete
      });// tweenlite instance
    }
    // toggle the state property
    this.setState({ menuExpanded: !menuExpanded });
    this.menuTl.reversed( menuExpanded );
  };

The basic stuff are the final two lines, basically the state property is being updated and we're using that to trigger the animation in order to keep consitency between react and the timeline reversed state. I'm using the reversed property because the timeline is paused in the constructor and when is created instead of playing it is reversed, with that we can toggle the reversed property of the timeline to toggle it's direction.

 

Then the part of each panel animation:

  togglePreviewCase = (index, e) => {
    const { menuItems } = this;
    const { targetPanel } = this.state;
    // create two arrays with the elements that should be animated
    // one for the elements that will be expanded and the ones that
    // will be collapsed
    let expandArray, collapseArray;
    // if the current target is null means no element is expanded
    if ( targetPanel === null ) {
      // only create an array for the elements that will be expanded
      expandArray = menuItems.slice(0, index + 1);
      TweenLite.to( expandArray, 1, {
        x: '-=50%', ease: Power2.easeInOut
      })
    } else if ( index < targetPanel ) {
      // the new target is already expanded, we have to collapse
      // the panels before that, there's no expand animation
      collapseArray = menuItems.slice(index + 1, targetPanel + 1);
      TweenLite.to( collapseArray, 1, {
        x: '+=50%', ease: Power2.easeInOut
      });
    } else if ( index > targetPanel ) {
      // the new target is not expanded, we have to expand all the
      // elements between the previous target and the new one
      expandArray = menuItems.slice(targetPanel + 1, index + 1);
      TweenLite.to( expandArray, 1, {
        x: '-=50%', ease: Power2.easeInOut
      });
    } else if ( index === targetPanel ) {
      // the current target element is being clicked, reverse that
      TweenLite.to( menuItems[index], 1, {
        x: '+=50%', ease: Power2.easeInOut
      });
      // the new target index should be the previous element if the
      // index is bigger than 0
      return this.setState({ targetPanel: targetPanel > 0 ? targetPanel - 1 : null });
    }
    // set the current index as the target panel
    this.setState({ targetPanel: index });
  };

The comments and the explanation for the use of arrays pretty much cover what's happening here. The idea is to keep track of the index of the element being clicked to create an array of elements that should be animated. Then depending on the index position of the target panel (where the user clicks) if those will be collapsed or expanded.

 

Finally the other part of the toggle menu code. If the user clicks on the toggle menu button and there's a menu panel expanded, we create a tween for all the expanded panels and when that's complete, reverse the menu tween and set the target panel to null using an onComplete callback.

 

Happy Tweening!!

  • Like 3
  • Thanks 1
Link to comment
Share on other sites

@Rodrigo,

I just saw your answer, and it is exactly what I was trying to achieve.

I cannot thank you enough, for your time and help on my journey with React and GSAP, which just started.

No problem with not reusing my code, it was a bit spaghetti anyway and needed a big refactor. that is what I was trying to do yesterday.


I have read the code with the explanation and still yet to assimilate and understand all of it better.

I will keep studying and following  GSAP and React tutorial from @Ihatetomatoes


@Sahil, Thank you for pointing to my error and suggested very wisely other route and issue in my code.

I have learned so much with you guys, discover an incredible community and you pushed me to keep trying harder (even if I could not come up with a full solution of mine)
I will be back at some point soon for more advice and share the final product that I am building and one day give back by answering questions from others.

in a meantime

giphy.gif

 

 

Happy Tweening to you all !!!!!

you guys rock

  • Like 4
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...