Jump to content
Search Community

Event handlers have no arguments - why?

Dave Stewart 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

It's a shame event handlers aren't passed any arguments:

 

See the Pen IsqiC by anon (@anon) on CodePen

 

Surely this is an opportunity to pass some handy default values, especially when scoping the handler to something other than the timeline you're animating?

 

An event object would be handy, or even just a few basic arguments, such as:

onUpdate:function(tween/timeline, progress, totalTime){ ... }

Was there a particular reason to omit this?

 

I feel a fork coming on... :)

 

Cheers,

Dave

Link to comment
Share on other sites

Hello Dave Stewart and welcome to the GreenSock Forums.

 

If you pass the onUpdateParams {self} .. that will pass the timeline and then you can do stuff with it. You can use any of the special properties to pass your arguments like onComplateParams, onStartParams, onReverseParams, etc...

 

Example:

See the Pen keEyq by jonathan (@jonathan) on CodePen

 

Notice the onUpdateParams special property with {self} :

var $circle = document.getElementById("circle"),
tl = TweenMax.to($circle, 4, {
            rotation:360,
            force3D:true,
            onUpdateParams:["{self}"],
            onUpdate:function(tl){
                // tl references {self} which is the timeline
                var tlp = (tl.progress() * 100) >> 0; // convert to %
                $("#progress").html(tlp+"%");
            }                        
  });

See TweenMax Docs for onUpdateParams:

 

onUpdateParams : Array - An Array of parameters to pass the onUpdate function. For example, TweenMax.to(mc, 1, {x:100, onUpdate:myFunction, onUpdateParams:[mc, "param2"]}); To self-reference the tween instance itself in one of the parameters, use "{self}", like: onUpdateParams:["{self}", "param2"]

 

Does that help? :)

  • Like 1
Link to comment
Share on other sites

Hi Jonathan,

 

Thanks for that.

 

That's certainly helpful, but boy, it feels a little dirty!

 

I'd rather some intuitive default values as suggested above, or an event object of mixed-in parameters rather than having to pass in a set of onEventParams values each time I create a timeline.

 

I guess it's too late in the product cycle to suggest a nicer alternative now, so I'll have to live with it.

 

What would "param2" be, the string "param2" or the .param2 property on the original timeline, or its .vars object?

Link to comment
Share on other sites

"Param2" would just be a argument that is a string since it is in quotes. And mc would be a variable instance. Basically once you pass in the timeline instance, in this case {self} .. then you can access anything within that timeline instance.. like: progress(), time(), totalTime(), etc...

Link to comment
Share on other sites

Hi Dave,

 

Adding to Jonathan's excellent advice, you should consider that if you create the following code:

TweenLite.to(element, 1,
  {
    top:300, left:300,
    onComplete:yourFunction(param1, param2)
  });

yourFunction will be called as the code is executed and not after the tween is completed, therefore the need of passing parameters through an array to the function being called, is just the way the engine is constructed and I'm pretty sure that Jack considered other alternatives but this is the best alternative in terms of performance and filesize.

 

Rodrigo.

  • Like 1
Link to comment
Share on other sites

Not sure if I follow you there Rodrigo.

 

yourFunction is passed by reference to the tween instance's vars object, and is called via Function.apply when the tween completes:

 

https://github.com/greensock/GreenSock-JS/blob/master/src/uncompressed/TweenLite.js#L1362

this.vars[callback].apply(this.vars[callback + "Scope"] || this, this.vars[callback + "Params"] || _blankArray);

Whether the parameters passed to the callback function reference are a predefined array or a predefined event object wouldn't make any difference to performance or filesize.

 

But I've no problem with that. The fact that GreenSock makes everything else so damn easy is good enough for me!

Thanks for the input though  :)

Link to comment
Share on other sites

Thanks for the feedback, Dave. And yes, it's always a tough balancing act when designing the API; we try to keep things very tight file size-wise and super performant while also being extremely flexible and robust. Over the years, there have been many cases that came up where we (or a customer) thought "gosh, it'd be nice if _____ feature was added" and it was perfectly valid and useful for that particular scenario but if we kept adding features that way, it could quickly get out of hand. 

 

I do like the idea of some sort of defaults that you could set on a timeline-specific basis. It could be nice to set a defaultEase for any given timeline too. But that'd also require adding conditional logic that'd look up the timeline chain searching for defaults (imagine a timeline within a timeline within a timeline) and then if none are found, use the engine's default. Not a HUGE deal, but when you consider how absolutely essential top-notch performance is for animation, and the fact that some people need to have hundreds or thousands of tweens all starting at the same time (thus that lookup and conditional logic runs for every tween on the same tick), I get concerned. Is it REALLY worth making everybody pay that "performance tax" so that a fraction of the audience could tap into that feature? Hm. Maybe, but I'm reluctant to add that, especially because once a feature is in there, it's almost impossible to remove it without causing all sorts of trouble for customers down the road who start depending on it. 

 

As for default scope, there are several ways to make this less cumbersome through your own code if you find yourself having to define the scope constantly. Here are a few ideas:

 

1: Use closure:

var scope = this; //or whatever
TweenLite.to(e, 1, {onComplete:function() {
    scope.method();
}});

No need to define onCompleteScope or params or whatever. Just use closure to lock the scope in. That's a pretty powerful piece of functionality baked into JavaScript that you can leverage.

 

2) Use a method that does the work for you and spits back an object with the appropriate scope:

function vars(vars, scope) {
    vars.onCompleteScope = vars.onStartScope = vars.onUpdateScope = scope;
    return vars;
}
TweenLite.to(e, 1, vars({x:100, onComplete:func}, myScope));
TweenLite.to(e, 1, vars({y:100, onStart:func}, myScope));

3) Create a function that does the binding for you with closure:

function callback(func, params, scope) {
    return function() {
        func.apply(scope, params || []);
    }
}
TweenLite.to(e, 1, {onComplete:callback(myFunc, ["param1"], scope)});

Just a few ideas.

 

I'll ponder the defaultScope thing for timelines and perhaps add it sometime in the future. 

Link to comment
Share on other sites

Thanks for taking the time to reply, Jack! (though I think you may have wanted to reply to the other thread).

 

Those are certainly all workable suggestions, if a little more verbose than a global default - but as you discussed above, everything has a reason.

 

Hope you had a good holiday,

 

Dave

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