Jump to content
GreenSock

erikb

unable to tween via proxy

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 am tweening values in vanilla javascript objects.  OnUpdate, I would like to 

TweenLite.set( $myElements, tweenedVanillaObject ).

However, as you can see here

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

this does not work.  The first update takes effect, but all subsequent updates do not. 

 

In that codepen, you can toggle proxy2 to run the vanilla object's tweened properties through another vanilla object, but this strikes me as ... kind of silly.

 

What is going on here and how do I remedy this situation for optimal performance.

Link to comment
Share on other sites

What's happening is something like this:

  • Create tween of tweenedObj to targetObj
  • First update tweenedObj gets a new scale value { scale: 0.999..... }
  • In onUpdate, create tween of $test to tweenedObj
  • Because $test is a DOM object, GSAP parses the tweenedObj vars looking for CSSPlugin values, finds 'scale' and automatically creates the css:{} object it requires internally for tweening styles. tweenedObj is now { css: { scale: 0.999 }, scale: 0.999 }
  • Next update tweenedObj gets new scale value, now { css: { scale: 0.999 }, scale: 0.888 }
  • In onUpdate, create tween of $test to tweenedObj
  • Since GSAP finds a css:{} object in the vars, it uses that as the next target values for $test, which would just apply scale: 0.999 again
You can get around this by deleting the newly added css property each update

delete tweenedObj.css
TweenLite.set( $test, tweenedObj );
  • Like 3
Link to comment
Share on other sites

Thanks Jamie -- that seems like a reasonable explanation and a good idea for a workaround. 

 

Does this issue warrant a bug report on the github repository?

 

Let me step back to explain why I am doing what I am doing: I have elements moving onto and off of the screen which need to have the current tween css values applied to them.  Hence my use of a vanilla proxy object.  Maybe there is a better way to dynamically change the targets of a tween than my use of a proxy?

Link to comment
Share on other sites

Nope, it's not a bug at all. No bug report necessary :) (at least as far as I can tell)

 

I'm still a bit fuzzy about what exactly you're trying to do. Can you provide a codepen? It looks like the one you provided first is just contrived and not like what you're actually trying to do (right?) 

 

Also, Jamie's solution is perfectly valid, but if it were me, instead of deleting the "css" property on each render, I'd just control that one to begin with. In other words:

var vars = {css:{scale:0.5}};

TweenLite.to(vars.css, 2, {scale:1});
...
function() {
    TweenLite.set(element, vars);
}

But again, I'm struggling to understand what you mean by "dynamically change the targets of a tween" - I'm pretty sure you don't mean the actual target elements of the tween. Do you mean the destination values? Are you literally needing to change them mid-tween? That's actually quite a bit more complex than you may expect due to the fact that tweens must be reversible too. We do have a TweenMax.updateTo() method but we don't officially support plugin-based values (it's a long story, but there are some important reasons why). 

Link to comment
Share on other sites

Alas, the example is not contrived.

 

We've many, many elements moving onto and off the screen which we need to update to the current state of an animation ( and keep animating) when on screen.  When these items are moved offscreen, we remove them from the rendering queue.  Trying to animate all of them, even with greensock, causes really poor performance.  Hence the use of the proxy.

 

Your suggestion of how to handle this makes sense to us!  Much cleaner than creating a new object every time.

 

It would be neat if there were a way to dynamically modify the target to remove the need for situations like ours.

Link to comment
Share on other sites

So are you saying that you need to be able to start a tween, and then later on you want to completely change the destination values WHILE the tween is running? For example, you start a tween like TweenLite.to(element, 1, {x:100}) and then maybe 0.75 seconds later you want to make element.x go to 500 instead of 100 but it still must apply the easing as if the tween started 0.75 seconds ago and will still finish in 0.25 seconds? 

 

Is the complication that you're trying to put it into a TimelineLite/Max and it has to be reversible to the original starting values? 

 

Sorry, I'm just unclear about what you mean by "dynamically modify the target to remove the need for situations like ours". Please be as specific as possible about a real-world scenario. I understand you're moving lots of things onto and off of the stage - that doesn't clarify it for me. I need to know what you're expecting to happen with the tweens, and what the current technique isn't doing that you want it to do. 

  • Like 1
Link to comment
Share on other sites

I truly appeciate your interest in our situation.  Here is a contrived scenario which hopefully helps eluciate our needs:

 

Imagine there are two elements -- element A and element B.  I create a linear tween for element A which modifies scale from 1 to 20 over 20 seconds.

 

When the user clicks/touches the screen, I want to stop tweening element A and start tweening element B using the same tween.  At the time of the click event, I want element A to stop tweening and element B to jump to the current state of the tween (so it would automatically change the scale of element B to x10 at ten seconds in) tween and continue tweening element B.  Element A would no longer be modified as it is no longer the target of the tween.

 

Further, I would like to be able to toggle back and forth between element A and element B or apply the tween to both elements or to neither element.

 

I hope that example is more helpful.  Thank you.

Link to comment
Share on other sites

Hi Erik,

 

You don't need to go into all that trouble for that particular feature.

 

What you can do is create two separate tweens for each element, with the same duration and parameters in the config object. The only difference is that the second tween is paused. You start playing the first tween and when the user triggers the event, you pause the first tween and play the second from the first's current time, like this:

var t1 = TweenLite.to("#div1", 10, {scale:10}),
    t2 = TweenLite.to("#div2", 10, {scale:10, paused:true});

$(window).click(function()
{
  t1.pause();
  
  t2.play( t1.time() );
});

No more fuzz than that thanks to GSAP ;). You can see it here:

 

See the Pen pAJya by rhernando (@rhernando) on CodePen

 

Rodrigo.

  • Like 1
Link to comment
Share on other sites

Great idea, Rodrigo. I suspect that'd perform better too. 

 

By the way, you could make the code SLIGHTLY shorter with:

t2.play( t1.pause().time() );

(because the methods return the timeline itself so that you can chain them)

Link to comment
Share on other sites

Okay, that makes sense.  However, mine was a toy example to elucidate my use of the word target to mean target element. Sorry for not being clear enough.

 

Let's expand the number of elements to 1k, and every second while playing the tween let's change the tween's target elements (meaning those elements to which the tween's updates will be applied).  1k elements on reload could be 1.5k or 2k... we have a data driven number of elements on every page load.

 

For this type of scenario, we've been using a proxy object (as described in the original post).

Link to comment
Share on other sites

That's fine - I'd just encourage you to do some performance testing because there may be a threshold where one solution is better/faster than the other. Especially if all the other tweens are paused, and you simply play() at a particular progress value, that might be faster than constantly using a TweenLite.set() on every single render because that creates a new tween instance, reads the current values, sets them appropriately, etc. I just have a feeling that it might be cleaner to swap to an existing tween every second rather than creating a new one 60 times per second. But it's tough to know for sure without doing some testing. I'd love to hear what you discover. 

  • Like 1
Link to comment
Share on other sites

  • 3 weeks later...

Here is a rough test -- 

 

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

 

When the buffer is compeltely full, i get 11-13fps using greensock and 16-17fps using jquery css. ($body size ~2000x~600, chrome on mac).

 

I am sure there are ways to optimize this code.  I am curious what you would suggest.  Thanks for your help in exploring this issue, and moreover, thanks for this library!

Link to comment
Share on other sites

and using pure javascript for updating the css, I get 19-20fps.

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

Link to comment
Share on other sites

There are several issues at play here...

  1. A TweenLite.set() is much more capable than jQuery.css() and requires a bit more processing because it must record starting values. Why? Because you could put a set() into a timeline which could be reversed, thus reverting the values. This is very powerful and most other animation engines don't allow it. GSAP does. So it's no surprise to me that you're seeing jQuery.css() go faster since all it has to do is set the value(s) (no parsing/recording necessary).
  2. You're likely running into a layout thrashing issue because your set() calls must read values and then set them, and then another tween reads/writes, etc. There's a new feature in the upcoming release of GSAP 1.12.0 that will let you set a special property to delay the writing of the values until the end of the "tick" (meaning they'll be batched). This might make a pretty significant difference for you. We hope to release 1.12.0 by early next week.
  3. Of course straight JS would perform faster, as you're cutting out the middle-man and don't need to create tween/jQuery objects, record starting values, etc. 
  • Like 2
Link to comment
Share on other sites

Much thanks Jack, and looking forward to the forthcoming new version.

 

If you could include a note in the new version about where the best practice for minimizing thrashing on onUpdate callbacks, that would be super.

 

For my problem (tweening updates to ever changing element targets), I am thinking it makes sense to tween a generic object with the css values I want applied to my current ever changing list of target elements.  Then, on every update, I will iterate over the vanilla object's updated keys and with vanilla javascript apply the keys and values to the currently-being-tweened-target-element.

 

However, I've hit a snag using a vanilla object with css values and trying to apply those updated values to dom elements.  Values like scale are not being transformed to -webkit-scale (for example).  

 

See the Pen Bikfy by jedierikb (@jedierikb) on CodePen

 

Is there a way to have greensock update a vanilla object with css values?  Or should I use a dummy object instead?  And if I use a dummy object, what is the recommended way of getting a list of the values greensock is tweening on that object?  

 

Much thanks.

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