Jump to content
Search Community

Callback function ceases to work when timeline in nested

robarnow test
Moderator Tag

Go to solution Solved by Carl,

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

Basically, I have a loop here in this codepen that cycles through a spritesheet in order to animate it. That spritesheet is the flying bird you see. Once the spritesheet runs through fully once, I was attempting to use "seek" in order to loop it so that only the flapping wings loop, rather than the entire animation. The function "loop" is called upon at the end of this animation to do this.

 

This worked properly before this timeline "bird_sprite" was nested inside of the timeline called "animation". Once it was nested, while the loop function is still called, it doesn't actually affect the "bird_sprite" animation in any noticeable way, so you'll see that the wings simply stop, mid-flight. I'm guessing this is some sort of scope issue, but I really don't know

 

Any help would be appreciated, and try to talk in laymans terms -- I'm a designer with some coding chops so I'm not as well-versed as most on here. Thanks in advance.

 

(I'm using array variables here, because the live site actually has multiple bird animations running simultaneously)

 

  function bird_sprite() {     // this is just the white bird, so this is nested in the next timeline "animation"

 
        bird_takeoff_sprite[bird_number] = new TimelineMax({ repeat: 0, onComplete:loop });
            
            for (var i = 0; i < spritecount; i++) {
             bird_takeoff_sprite[bird_number].set(image[bird_number], { left: "-" + (i*spritesize) + "%" }, i*duration);
            }
            
            <!--this loops the flapping wings but not the whole animation-->
            function loop() {
                 bird_takeoff_sprite[bird_number].seek(.8);
                console.log('loop'); // this proves the loop function runs
                }
                
                
    return bird_takeoff_sprite[bird_number];
  }
        
// this is the full animation
        animation[bird_number] = new TimelineMax({ repeat: 0, paused: true });
 
        bird_container = $(this);      
 
        animation[bird_number]
            .add( bird_sprite())  // nesting the sprite animation of the bird
            .to(bird_container, 3, {    left: "-300%",
                        width: "+=180%",
                        height: "+=200%",
                        bottom: "+=10%", ease:Sine.easeIn}, .8);
          
        
    });
  };
}(jQuery));
 

See the Pen GggZzW by robarnow (@robarnow) on CodePen

Link to comment
Share on other sites

  • Solution

Hi and welcome to the GreenSock forums,

 

Thank you for providing the demo. Very helpful. 

 

Before I get into a big explanation of the fix, can you let me know if this working for you: http://codepen.io/GreenSock/pen/XJJKrL

 

The only thing I changed is that I added a smoothChildTiming parameter to your main timeline

 

animation[bird_number] = new TimelineMax({ repeat: 0, paused: true, smoothChildTiming:true });

Link to comment
Share on other sites

Thanks so much, that fixes it. I read the docs on smoothChildTiming though, and I understand what it does with "reverse()" where it flips the child in place, but with smoothChildTiming set to false and attempting to reposition the child's playhead by using play() or seek() does anything happen at all, or does it simply ignore the command?

 

Also, I'm assuming that with smoothChildTiming set to true, that the repositioning of the child does not affect the timing of any other elements on the timeline? So therefore the callback function, in this case called "loop", is not included in the total duration of the child animation, and the amount of time added by the repositioning of the playhead on the child needs to be calculated manually by the programmer in order to be accounted for in the main timeline?

Link to comment
Share on other sites

Hi robarnow,

 

smoothChildTiming is probably one of the more advanced "behind the scenes" things that GSAP does, and folks really only need to care about it when they run into a situation just like yours. 

 

Since you have a good background understanding of how smoothChildTiming works with reverse, here's my short version

 

Keep in mind that all child timelines render based on where their parent timeline's playhead is. The works out in most cases as when you play the parent forward, the child plays forward with it. Reverse the parent, the child appears to play in reverse. 

 

In your case you were playing the parent forward BUT telling the child to go back in time to its 0.8 time. So even though you were technically moving the child's playhead back, when it came time to render changes they were based on the parent's playhead position. Your console logs were very helpful in this case as you probably noted that once you started firing your loop() function you saw the child was completing many many many times in quick succession. This is because this was because even though the child's playhead was always moving back to 0.8, the parent was always jumping it forward PAST a point in time that the child would have completed.

 

In this situation smoothChildTiming:true tells the parent timeline that when you tell the child to play(0.8) or seek(0.8) the child timeline will literally get picked up and its startTime will be moved forward so that the local playhead position of the child (0.8) aligns with current position of the parent's playhead (thus keeping both timelines moving forward in perfect synch).

 

Make sense?

 

Hopefully ;) and if not, I understand. This is the type of thing we probably need charts and animated demos to convey properly.

 

And if you (or others) really want to geek out...

 

Here is the long version that Jack recently provided to another user in a similar situation ;)

 

Why didn't my child tween (or timeline) render properly when I tried to call seek() on it?

When a timeline’s playhead moves to a new position (which typically happens 60 times per second or whenever you seek(), etc.), that timeline simply loops through all of its children (tweens and nested timelines) and tells them to render at that particular spot (wherever the playhead is). Thus, everything is always perfectly in sync, and updating a parent timeline has a cascading effect, pulsing that time change down to all of its children accordingly, who do likewise to any of their children. 

 

The only exception is a paused state – that effectively locks the animation's playhead in place, regardless of where its parent timeline’s playhead moves. 

 

This is the beauty of the architecture – GSAP doesn’t have to keep a master list of every single tween and manually update each one on every tick - there’s just one root timeline that gets updated 60 times per second and those changes pulse down to the children who update their children, etc. and if tweens are tucked into a timeline that hasn’t started yet, they don’t even get touched. 

 

The root timeline has a playhead that’s always moving, of course – think of that like the document’s or window’s time. When you create a tween or timeline, it drops it onto that root timeline with a startTime of…whatever time it is now. Then it “plays” because the root’s playhead moves and updates the children’s playheads. That’s all transparent to you, the user. 

 

Also note that if you pause() a timeline, none of its children will appear to play (even if you play() them directly) because the parent timeline’s playhead isn’t updating/moving. This is VERY convenient actually because if you have a bunch of tweens that you want to pause, you don’t have to loop through them all and call pause() independently – you can just pause the parent timeline. 

 

Let’s say you have a 5-second tween that just started (playhead is at 0), and then you seek(2). We must keep the playhead synchronized with the root playhead so that as it pulses its changes down to all the children, things render properly. So that seek(2) just picks the tween up and moves it back 2 seconds so that the playhead aligns exactly where we want it! The same sort of thing happens if you reverse() a tween – GSAP will quickly do the math to figure out what the new startTime should be to make the playhead line up perfectly with where it currently is in the parent. 

 

One key exception – since when most people build their own timelines (TimelineLite or TimelineMax), they have very particular timing in mind, so they don’t want any of this automatic shifting and aligning of playheads – that’s why smoothChildTiming is false by default (except on the root timeline). That means that GSAP treats all of your startTimes as locked/frozen in timelines that you build. You can set smoothChildTiming to true on your timeline(s) if you want. 

 

Hopefully your head isn’t spinning at this point.

 

Here’s my guess at what was happening to you: 

I bet you tried to seek() a child timeline but it looked like it didn’t work because on the very next tick/render, the parent’s playhead pulsed its change down to your nested timeline and put it back where it was (well, a little ahead since time passed). 

 

Let’s say you have a master timeline whose virtual playhead is at a time of 2 seconds, and you nest a timeline such that it STARTS 3-seconds into that master timeline. So basically the master’s playhead is lined up 1 second before that nested one starts…

 

|----- master ----------------- |

      | <—playhead

            | ----- child ----- |

 

If you child.seek(0.5) at this point, it would technically misalign the playheads because the master would be at 2, but the child would be at its local 0.5 which is actually the parent’s 3.5-second point. With smoothChildTiming:true, it would move that child’s startTime back to 1.5 so that the playheads line up perfectly. But with smoothChildTiming:false, it will quickly render the child at 0.5 as you requested, but then on the very next tick since it didn’t move the tween, the parent’s playhead will update to…let’s say 2.01... and pulse that down to the child which hasn’t moved, so that’s before the tween is scheduled to start and it’ll look like your seek(0.5) did nothing even though this is all exactly how things should work. 

 

Again, sorry if all this was confusing. There are very good reasons for all of these choices in the architecture that may not seem immediately apparent. You don’t really need to understand the parts about smoothChildTiming or moving the startTimes, but it would definitely help if you understood the concept of how nested timelines work from a rendering standpoint and how everything hinges on the parent timeline’s playhead pulsing down to the children. 

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