Jump to content
GreenSock

MarcoBarbosa

Make sure onComplete callback runs, even if Tween is killed?

Go to solution Solved by jamiejefferson,

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 all,

 

Is there any way to make sure a onComplete callback always runs?

 

I started by using the onComplete callback of TweenLite but since I kill any previous tweens, the onComplete callback may never get the chance to run.

 

Example:

function show ($view, onCompleteCallback) {   
    TweenLite.killTweensOf($view);
    TweenLite.fromTo($view, 0.3,
        {
            opacity: 0,
        },
        {
            opacity: 1,
            ease: Quad.easeInOut,
            onComplete: onCompleteCallback
        }
    );
}

So I ended up using a setTimeout or TweenLite.delayedCall (which essentially is a better looking setTimeout? is it?). Like so:

 

function show ($view, onCompleteCallback) {
    TweenLite.killTweensOf($view);
    TweenLite.fromTo($view, 0.3,
        {
            opacity: 0,
        },
        {
            opacity: 1,
            ease: Quad.easeInOut
        }
    );
    TweenLite.delayedCall(onCompleteCallback, 0.3);
}

Is this the "proper" way to do it? Is there any way to make sure the onComplete of a tween runs even if I kill previous Tweens?

 

The complete callback here must run in order to perform some important tasks (such as removing listeners etc) - so I can't rely on the onComplete (apparently?). However I do have to base it on the tween because it shouldn't happen until it's completely visible in the view (tweened).

 

And the reason I do killTweensOf before the Tween is just in case the user clicks around too fast: so it immediately stops whatever is being tweened and tweens only the last interaction.

 

Is using the Timeline any different for callbacks? Any improvements suggestions here?

 

 

Link to comment
Share on other sites

  • Solution

Well you can rely on onComplete, but if you kill() the tween before it has completed... that's not the same as completing the tween. You can make sure a tween has been completed first by calling progress(1) before killing it. e.g.

foo.progress(1).kill(); // renders foo's end state, onComplete is triggered immediately, then foo is killed
This won't call the onComplete again if the progress was already at 1 (i.e. 100% played). Whether you see that as 'safe' in your situation is up to the architecture of your program.

 

In your example, you can use TweenLite.getTweensOf($view) to get an array of tweens, then iterate over them in the above manner e.g.

var tweens = TweenLite.getTweensOf($view);
for (var i = 0, l = tweens.length; i < l; i++) {
  tweens[i].progress(1).kill();
}

One other option if you don't want to render the end state would be to access the onComplete function directly e.g.

var tweens = TweenLite.getTweensOf($view);
for (var i = 0, l = tweens.length; i < l; i++) {
  var t = tweens[i];
  if (t.progress() !== 1) t.vars.onComplete();
  t.kill();
}
  • Like 3
Link to comment
Share on other sites

Thanks Jamie.

 

I see your point. Yeah, you can rely on the callback unless you kill it. It does makes sense :-)

 

But then I question how can you have callbacks, specifically "onStart" and/or "onComplete" that rely on the tween to know when they should happen, yet always run it - regardless of being killed or not.

 

Looks like I'd need some extra code to either kill it the way I want (by running the callbacks).

 

In that case I'd rather rely on "delayedCall":

TweenLite.delayedCall(onCompleteCallback, 0.3);

Unless there are alternatives for this?

 

It would be the same with Timeline, I suppose?

Link to comment
Share on other sites

Yes, you should definitely use TweenLite.delayedCall() rather than a setTimeout() because the delayedCall is guaranteed to be perfectly synchronized with everything else in GSAP whereas the setTimeout() is far less predictable and might end up running a few ms before the end of your tween or something. 

 

A few more tips/comments:

  1. You don't have to call TweenLite.killTweensOf(target) right before your animation - you can simply add overwrite:true to the tween which accomplishes the same thing in a more efficient way. 
  2. You were using the wrong order of the parameters in delayedCall(). The delay comes first, then the function ;)
  3. If you want to be able to "lock" the onComplete to the tween without actually having it attached to the tween itself (thus getting killed along with the tween), you could use a TimelineLite like this:
    var tl = new TimelineLite();
    tl.fromTo(...).call(yourOnComplete);
    That way, you could control the entire thing as a whole, pause(), reverse(), whatever and that callback would be positioned to run immediately after the tween finishes.
  • 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.
×