Jump to content


onComplete is firing before the last animation frame

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


I have created a small demo:



The problem is that onComplete is firing before the last animation frame (example: duration=60 frames and onComplete is firing on 59th frame).


The problem is that when I am hiding element after onComplete event and starting a new animation with 60 frames delay there is a small visual bug (1 blank frame) between this two animations.


Please let me know how this issue can be fixed.

Thanks in advance

Link to comment
Share on other sites

I may be misunderstanding you, but from what I can tell the behavior is exactly what it should be. Remember, the engine updates on each tick (renders all the tweens) and THEN immediately dispatches a "tick" event, so your listener would execute right after that (on the same frame). That's by design - typically people want to run their code immediately after the engine updates all its values and renders things accordingly. 


So I'd expect your onComplete to fire on the 60th frame and then your listener would get called. Your code is incrementing the frame number inside the listener, so the order of things look correct to me (log frame 59, then the next frame happens and the onComplete runs, then you log frame 60). 


Am I missing something? 

Link to comment
Share on other sites

Let me describe the issue more clearly:

There are two identical red boxes in the same position.

Both are invisible before animation starts.

First box is showing for 60 frames then in onComplete function it becomes visible false.

Second box has delay 60 frames.

The problem is that there is a blank frame between this two Tweens. 


How this can be fixed?

TweenMax.to(item, 60, { useFrames: true, visibility:'visible', onComplete: onCompleteFunc});
TweenMax.to(item2, 60, { delay:60, useFrames: true, visibility:'visible'});

function onCompleteFunc(){
    item.style.visibility = "hidden";
Link to comment
Share on other sites

I can see a slight discrepancy between time based and frame based animation here:


http://jsfiddle.net/Tu9nR/1/  // time

With time based tweens, the completion of the first tween and start of the next tween are aligned on a single tick.


http://jsfiddle.net/Tu9nR/  // frames 

With frames, the completion of the first tween and an update? of the second tween occurs on the same tick, then the second tween start occurs on the following tick...

  • Like 1
Link to comment
Share on other sites


This issue happens only when useFrames is set to true.

Anybody can tell me how to catch real onComplete event after the last animation frame render?

Thanks in advance!

Link to comment
Share on other sites



Since this is pretty much how the engine's mechanism works (as Jack explained) you could try by adding an extra frame to the duration of the tween, like that your tween will last 61 frames, the next one will have a 60 frames delay and there shouldn't be any blank frame between tweens.

TweenMax.to(item, 61, { useFrames: true, visibility:'visible', onComplete: onCompleteFunc});
TweenMax.to(item2, 60, { delay:60, useFrames: true, visibility:'visible'});

function onCompleteFunc(){
    item.style.visibility = "hidden";

You can see it here:





  • Like 1
Link to comment
Share on other sites

  • 2 weeks later...

I just wanted to follow up and explain what's going on here...


First, let me establish a few things:

  1. Normally, tweens do NOT render when the virtual playhead is EXACTLY on top of the starting point (unless, of course, the playhead was moving backward). This is very intentional, because it's generally a complete waste of CPU cycles. Imagine obj.x is 0 and you create a 1-second tween to make obj.x end at 100. Forcing that tween to render immediately when it's created (and/or the playhead is right at the beginning) would simply set it to what it already is (0 in this case). GSAP is extremely optimized; lots of little things like this add up to make it fast. 
  2. An onStart fires when the playhead moves from 0 to some positive value (even 0.00001). It does not fire when the playhead is exactly at zero (because that means the tween hasn't really "started" yet ("start" implies that the playhead moved)
  3. CSS tweens always record their beginning values internally so that when the tween is rewound to exactly the beginning, the original values can be swapped in. 
  4. When useFrames is true, the playhead lands on exact, whole integers (frames) as it goes whereas normal time-based playhead almost never lands exactly on a whole value. 
  5. GSAP's timing is extremely precise (more so that almost any other engine I've seen). Many engines simply treat a delay as either expired or not, and when it expires, they start the tween from the very beginning. GSAP, however, treats delays as precise scheduling instructions and renders tweens as if they started when they were supposed to (even if that's in the past). For example, let's say you have a delay of 0.5 seconds and the browser happens to be rendering at a perfect 30fps (very unlikely - typically fps goes up and down a bit, but tries to hover around 60). So every 0.033 seconds, things render. The 15th render would land at around 0.495, and the next tick happens at 0.528 seconds, and the start time of our tween was inbetween tick 15 and 16. GSAP renders the tween 0.028 seconds in rather than 0. Many other engines just start 0.028 seconds late. This can lead to weird clumping of tweens like you can see in the speed test at http://www.greensock.com/js/speed.html. Select a large number of dots and a non-GSAP engine. You should see the clumping/rings whereas GSAP's dots are nicely dispersed. 

Due to #5, when normal time-based tweens start, the playhead is almost always just past their beginning spot (it rarely lands on top of the start time), but in useFrames tweens the playhead always lands precisely on the start time. 


In your example, you set visibility:"visible" in your 2nd tween, but when the playhead is EXACTLY on the beginning of that tween, it's supposed to show the original values (which, in this case is "hidden"). Then, on the very next render, the tween says "okay, let's do our magic and calculate the change...." at which time it set it to visible.


Again, the key distinction here is where the playhead is landing for the renders. Technically this is all the correct behavior (from what I can tell) - it's just a matter of wrapping your head around why :)


One other solution would be to specify the "from" value so that you get around the "render the original values at the start" conundrum (because in your case, you're not wanting the original). Like:

TweenLite.fromTo(item2, 3, {visibility:'visible'}, { delay:3, useFrames: true, visibility:'visible', immediateRender:false});

Notice I set immediateRender to false so that it doesn't force the "from" values to get applied right away. 


I hope this adequately explains what was happening. If anyone has ideas for somehow improving the behavior, let me know. 

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