Jump to content
Search Community

Zero duration 'visibility' tween on a timelineme

Gabriel 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

Using TimelineMax and TweenMax, when doing a zero duration tween to adjust an object's visibility at a point on a timeline, the visibility is toggled immediately instead of in sequence.

 

Example

var text = some text object
var img = some image object

img.css('visibility','hidden')

var tl = new TimelineMax()

tl.to(text,3,{left:500})

tl.to(img,0,{visibility:'visible'})

tl.from(img,3,{left:500})

The image object will be visible at the start of the timeline animation, instead of after the text object animation. Although it works if you do:
 

tl.to(img,0.001,{visibility:'visible'})
Link to comment
Share on other sites

Hi Gabriel,
 
Try using immediateRender:false.

 

The thing is that zero duration tweens and instances that use the set, from and fromTo methods, set the values assigned in the vars immediately, therefore once the tween is created, independently of the timeline particular sequence, your img element's visibility is set to visible.

 

Your code could be like this:

var tl = new TimelineMax()

tl
    .to(text,3,{left:500})
    .to(img,0,{visibility:'visible', immediateRender:false})
    .from(img,3,{left:500});

Hope this helps,

Cheers,

Rodrigo

  • Like 3
Link to comment
Share on other sites

I'm still encountering inexplicable behavior. Here's a different example:

var text = some text object
var img = some image object

var tl = new TimelineMax(repeat:-1)

tl.to(text,3,{left:500})

tl.from(img,0,{opacity:0})

On the first loop, 'img' will be visible the entire sequence. But on the next loop, 'img' won't be visible and will appear after 'text' animates, as expected. Each loop should be the same, but the first loop is different than the rest.

I'm encountering all kinds of problems with zero duration timeline tweens, I think something is buggy.

Link to comment
Share on other sites

I'm trying to build a dynamic animation function that swaps two div's using different transition effects. The idea is to tl.to the old div out of the way, and tl.from the new div into place. But because of this behavior, it can't handle tweening the new div when I use a zero duration. The only workaround is bloating my code to add a zero duration specific exception using:

tl.to(obj.css('opacity',0),0,{opacity:1, immediateRender:false})

Which means not using tl.from and setting it to zero opacity first, and only when zero is passed as the duration. Either that or parsing zero durations as 0.001

Link to comment
Share on other sites

Your 2nd-to-last post you had: 

var tl = new TimelineMax(repeat:-1)
tl.to(text,3,{left:500})
tl.from(img,0,{opacity:0})

The behavior you described is the expected behavior. Remember, all from() tweens render immediately by default. In your case, since it's a zero-duration tween, rendering immediately would put it at the end of the tween (0), so the end value rendered. It's a from() tween, so that means it basically runs backwards, so if your img starts at opacity:1, that's where it'll end. That tween gets dropped into the timeline at the very end. So when the timeline finally finishes, the playhead hits that tween and then when it goes back to repeat, the playhead "rewinds" that tween back to its beginning (actually before its beginning), so opacity goes to 0. 

 

It sounds like the behavior you want requires immediateRender:false. 

 

I must admit that I think this is the first time I've ever seen someone use a zero-duration from() tween. I'm having a tough time understanding why. Typically from() tweens are only used to animate things into place. If you just want to set a particular value, it's best to use a set() method. 

 

As for your last question, I had a hard time visualizing what's going on - can you post a very simple codepen or jsfiddle or HTML doc that clearly demonstrates the issue? I'd be happy to take a peek. 

Link to comment
Share on other sites

It's not acting the way you claim. Yes, "timeline.from" tweens render immediately, that 'is' the behavior I want. And when I substitute the 0 duration with say, 0.001, then the animation runs exactly the way I want and expect. But, if the duration is 0, then on the first loop of a repeating timeline, it is NOT rendering immediately. So, If I want to animate tl.from(img,0,{opacity:0}), the image will be visible for the entire first loop, and then go hidden and work as expected for the second loop, so first loop !== second loop.

My functions are too big and interconnected to paste here, and a codepen to explain the simple concept of what I'm doing would take too long. It's very simple. I have a banner animation:
 

<div id="banner">
   <div id="layer1">stuff</div>
   <div id="layer2">stuff</div>
   <div id="layer2">stuff</div>
</div>

The goal is to, very simply, swap the layers in sequence during the timeline. Now, instead of simple swaps, I have animations. One layer may slide left, another fade into view, etc. And to facilitate this, I've made a function that is like:
 

function(timeline,delay,{oldLayer transition args},oldLayerAnimationDuration,
     {newLayer transition args},newLayerAnimationDuration,animationsOffset)


So, as you can see, you just plug in the values you want. If you want the old layer to slide left, but want the new layer to immediately pop into view, then you should be able to put in a zero duration for newLayerAnimationDuration, which would use a from tween to put it into view. If you used a fade animation, then it would be animating from a 0 opacity.

So, in conclusion, 'from' tweens are broken when you use a 0 duration, regardless of how you set immediateRender. It's not providing the consistent behavior one would expect, leading to a need for coding hacks for when someone comes along and uses zero for an argument.

Link to comment
Share on other sites

And "timeline.to" is performing the exact same behavior, not applying on the first loop the same as the second loop. immediateRender:false fixes it in this case. Although IMHO, it should default to false. It should behave the same whether you use 0 or 1 for the durations, and shouldn't drastically alter it's behavior based on the duration used. This is not consistent at all.

Link to comment
Share on other sites

HI Gabriel,

 

The point is when you create a tween with a zero duration inside a timeline with a repeat:-1 (infinite repeats), the behaviour you're getting is the expected.

 

When you create your object in the DOM it CSS visibility property is set to hidden by code (I'm assuming you're still using the code from your first post), until that point you can't see that object. Then you create the timeline and your object is still hidden. Finally you create the tweens inside the timeline and, even if your timeline is paused, the zero duration tween is rendered immediately. Then the timeline completes it first cycle and goes for the next one, at that point the engine takes all the original values it recorded before and starts again, in this case the original value the engine has is visibility:hidden for that particular element, so that is why in the first loop the image is visible.

 

That can be solved by using immediateRender:false as part of the tween's vars, you can see a very simple example here:

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

 

The image on the left has immediateRender:true as default, and the one to the right has immediateRender:false

 

Also if you use the set method curiously the immediateRender:false is not needed, any reason for that Jack and/or Carl?, you can see it here:

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

 

Hope this helps,

Cheers,

Rodrigo.

Link to comment
Share on other sites

Please read my last message carefully. I noted that immediateRender:false works correctly on 'TimelineMax.to' tweens. As a side note, I made the argument for the developers that the duration should not change the behavior. My argument stands, even if this is 'expected' behavior.

 

Second, please read my 2nd to last message carefully. Even if an object is not set as 'hidden' initially, and you tween it FROM a 'hidden' state, a "TimelineMax.from" tween will NOT render it immediately 'hidden' when using a zero duration. Even when explicitly setting immediateRender:true. And it behaves this way with other properties.

Link to comment
Share on other sites

Good questions, guys. Let me explain...

 

Zero-duration tweens are very tricky for several reasons:

  1. A "tween" by its very nature implies some sort of elapsed time, but zero-duration ones have no time whatsoever; if the playhead lands right on top of one, should the end values render or beginning values? We've worked around this with some fancy footwork under the hood (mostly sensing the momentum of the parent's playhead).
  2. If you render at time 0, for all other tweens that's the beginning, but for zero-duration tweens, that's actually the END of the tween. So immediateRender:true would cause the onComplete to fire and it would mark the tween as inactive (appropriately so). 
  3. For all "normal" (not zero-duration) tweens, the first render occurs on the very NEXT frame/tick (after the tween is created, assuming there's no delay of course). This is very intentional because it improves performance and permits some of the more advanced overwrite modes to work properly. Let's say, for example, you're tweening "top" from 0 (what it is currently) to 100 over 1 second. It wouldn't make any sense to force a render immediately when the tween is created because no time has elapsed, so no change would happen and you'd waste CPU cycles. However, for a zero-duration tween, there's no concept of elapsed time, so we actually DO want to render immediately so that the end values take effect right away. Therefore, the logical behavior in most cases is to have immediateRender:true, but only for zero-duration tweens. 
  4. In practical usage, I've seen a lot of people using zero-duration tweens just to set values because GSAP made things so convenient. For example, it handles all the vendor prefixes automatically and does fancy stuff under the hood to solve browser differences with opacity and IE8 transforms, etc. And in the developer's logic flow, they were expecting the zero-duration tween to take effect immediately (that seems obvious when you're coding it), therefore if we DIDN'T set immediateRender to true by default, their code wouldn't work as they expected and quite a few developers complained. So we set immediateRender:true as the default for zero-duration tweens. That actually seems quite intuitive.

So, for example, some developers would do something like this:

TweenLite.to(element, 0, {top:"100px"});
doStuff();
function doStuff() {
    if (element.style.top === "100px") {
        //do stuff...
    } else {
        //do other stuff
    }
}

They would of course expect that zero-duration tween to take effect immediately and their doStuff() function would evaluate correctly such that element.style.top is 100px. 

 

See why zero duration tweens are so much different and why immediateRender:true makes sense as the default behavior? 

 

It's more tricky, though, when you're populating a timeline because typically you're scheduling things to happen in the future. That's one of the reasons we have the "set()" convenience method in TimelineLite and TimelineMax - it creates a zero-duration tween but it automatically sets immediateRender:false unless you're inserting it at the current time. It's another way the engine is doing work for you to make things more intuitive. 

 

So the moral of the story is that you should use the set() convenience method instead of creating your own zero-duration tweens, not only because it makes your code more concise, but it also handles the whole immediateRender thing for you automatically. 

 

I do see your argument about the duration not changing the behavior, and I think the only way this could be solved is to add code inside the to() and fromTo() convenience methods that senses if the duration is zero, and if so, it just redirects things automatically to the set() method which handles the immediateRender logic for you. I have attached a prerelease version of 1.9.7 with that change in place - does that help?

 

Also, you asked about the from() tween not rendering immediately even with immediateRender:true set, but I think you might be missing the logic there because in a from() tween, you define the BEGINNING values and the tween uses the current ones as the END values. But for a zero-duration tween, rendering it immediately means rendering it at a time of zero which is the END of the tween, so of course you'd never see the values change - they'd jump right back to the current values. See what I mean? That's why I was so confused by the fact that you're using a from() tween with a zero duration. 

 

greensock-v12-js.zip

  • Like 3
Link to comment
Share on other sites

Hi,

 

I read your posts and I understand what's going on, the thing is that I tried to put together a sample of your initial request thinking that in that way you could solve your issue.

 

As far as the zero duration tween using the from() method, it is behaving correctly.

 

I'm going to presume that your CSS states that the element's visibility property is set to visible, so before you create the tween the element is visible. Then when you create your TweenMax.from() with visibility:hidden in the vars, the engine understands that you want the property changed from hidden (the value passed in the vars) to visible (the original property value) and the engine does so over a time span of 0 seconds, ie, immediately, so as soon as the tween is created is executed and completed, that is why you have to use immediateRender:false and that is why I referred to your first post because I felt is the same issue now as it was then, just with the from method now and I wasn't clear enough in order to explain why it was happening.

 

I hope this helps,

Cheers,

Rodrigo.

  • Like 2
Link to comment
Share on other sites

  • 1 year later...

I'm sorry to resurrect this thread, but haven't anyone noticed dramatic loss of performance when using zero-duration tweens?

For example, I use zero-duration tween to set some attributes, and measure function execution time with Firebug console.time method.

 

It shows me 12-18ms execution time.

When I do the test with "immediateRender:false" added, it shows 0.3-1.2 ms.
The same happen when I use .set method instead of .to  - exactly the same behaviour, slow by default and fast with "immediateRender:false", though, as Jack said above, .set is suggested to have "immediateRender:false" by default.

 

Here's a Codepen you can test in FF with Firebug console

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

Link to comment
Share on other sites

This sounds like expected behavior. Let me explain a few things:

  1. By default, set() renders immediately which is pretty intuitive if you think about it - you'd expect that a set() would take effect right away so that the very next line of code could run logic against whatever you just set. 
  2. If you set immediateRender:false, the tween doesn't instantiate until the next "tick". So it'd make total sense that you'd see much shorter execution times on that set() call because it's not really doing anything (besides creating the tween). The DOM isn't queried for the current values, nothing is getting set, etc. 
  3. If you do a bunch of set() calls within one "tick", you can run into what's called "layout thrashing" which happens when values are read from the DOM and then written to the DOM and then that cycle is repeated. read/read/write/write is much faster than read/write/read/write. Please check out http://greensock.com/gsap-1-12-0for details and a video explanation of some enhancements we dropped into the engine a few versions ago. 
  4. A set() call is technically more expensive than a simple $().css(...) because a GSAP tween can be placed into a timeline where the playhead can go back and forth over that tween (and a set() is just a zero-duration tween), thus it MUST record the starting values too. That means a "read" is required. You get more functionality with a GSAP set() than a jQuery css() call. It's not that GSAP is just slower or anything - it's super optimized, but jQuery doesn't have all the functional requirements that GSAP does.
  5. Have you tried using lazy:true instead of immediateRender:false? That'd basically make it render at the end of the current tick instead of at the beginning of the next tick, which probably isn't really much different visually I guess. It batches things a bit differently though. 
  • Like 2
Link to comment
Share on other sites

Thanks Jack! So, correct me now if I am wrong:

 

1. set() method by default has immediateRender:true when used immediately upon creation and immediateRender:false when inserted in the timeline? I'm referring to this phrase:

 

...one of the reasons we have the "set()" convenience method in TimelineLite and TimelineMax - it creates a zero-duration tween but it automatically sets immediateRender:false unless you're inserting it at the current time.

Because in my example it behaves differently in default case and when {immediateRender:false} is set.

 

2. So, in fact, total execution time is the same in any case, the difference is just that with {immediateRender:false} its 'render' part just doesn't get measured, because it happens on the next tick;

 

4. Jquery toggleClass method is really several times faster (it shows about 1 ms execution time, when measured in a function like this)

function toggleJQ(trigger, target, className) {
$(trigger).on('click', function() {
console.time('JQ');
$(target).toggleClass(className);
console.timeEnd('JQ');
});
};

but GSAP has extended functionality. So, for simple tasks (instant class switching, for example) JQuery toggleClass is preferable compared with zero-duration tweens, but for more complex things with animation involved GSAP is the way to go? I'm just trying to decide which tool to use in which situation. Or am I incorrect, maybe JQuery does render on the next tick as well and I'm comparing apples to oranges?




 

Link to comment
Share on other sites

1) I'm confused - it sounds like you think a TweenMax.set() was NOT supposed to act differently whether or not you set immediateRender:false. Is that the case? I tried to explain that it very much makes a difference. Perhaps you were confusing the timeline class's set() feature that automatically handles immediateRender based on where you're inserting it into the timeline? Your code didn't use any timelines though.

 

2) That's correct as long as there's no layout thrashing.

 

4) I should have mentioned this earlier - className is a very special case because when you use GSAP to control a className, it literally has to set the class and then analyze every property and discern which ones changed and then set up the start/end values internally for just those. So it's doing a LOT more work than jQuery which is merely setting className = whatever. It's not tweening anything. It has almost no work to do, whereas GSAP is actually setting things up to be able to interpolate between any changed values (and again, it has to do all the comparisons). This is only the case with className tweens. 

 

You were definitely comparing apples to oranges in this case. You were measuring how much time it takes for jQuery to literally do element.className = "red" which doesn't even factor in the time it takes for the browser to render the changes, whereas you were measuring how long it takes GSAP to create a new optimized tween, record every style property value, apply the class, compare every style property to the originals, create the shortest list of only the changed values, and set them all accordingly after doing any interpolation that's necessary, and be ready to instantly go backwards/forwards and toggle the values as quickly as possible thereafter. 

 

So yes, if all you're doing is toggling a className, don't use an animation engine - use plain JS or jQuery or something. 

 

Does that clear things up?

  • Like 1
Link to comment
Share on other sites

1. Well, yes, somehow I thought that "it creates a zero-duration tween but it automatically sets immediateRender:false" meant that set() method differs from .to() in the aspect that (for zero-duration) .set has 'immediateRender:false' by default and .to has 'immediateRender:true'.
In that case, setting immediateRender:false explicitly would make no difference in comparison with default .set().
Should have read more carefully.

4.Oh yes, now it's getting clearer) Sorry if i'm asking obvious things, I'm still a newbie but trying to learn.
So, there's no sense to compare this way. For simple class switching JQ will do the job, but what about short animations?
For example, I can use CSS class + JQuery toggleClass + CSS3 transition to morph some property, or I can use CSS class + GSAP className to toggle it with morphing. Is JQuery approach better again?  I'm concerned because:
1) I don't like when animation parameters are spread across .css and .js files (JQuery in js file, transition parameters somewhere in .css) - using one function to rule them all is more comfortable IMHO; 2) For svg and IE, only GSAP method will work, and I'd like to go with one tool for DOM and SVG for consistency. From this point of view GSAP is preferrable, but the question is how slow it will be comparing to JQuery/CSS method in this case.
For animations that do not involve classes switching GSAP wins all the way, if I get it right.

Link to comment
Share on other sites

4) No problem - your questions aren't dumb or obvious at all. Totally valid. 

 

As far as GSAP vs. toggling a CSS class and using CSS transitions, it really depends. In this one case (with className), since GSAP has to loop through all the properties and do the comparisons, it would very likely be faster to toggle a class with jQuery and let a CSS transition handle it. However, there are some down sides to that approach too:

  1. As you said, it splits your animation logic/code into two different layers - some in JS and some in CSS. Might feel a little clunky when you need to make changes. 
  2. You lose all the nifty controls that GSAP offers. You can't, for example, have that tween in a master timeline that you scrub back and forth or alter the timeScale or whatever. If you use CSS transitions, you're pretty much limited to "fire it off and forget it". 
  3. You cannot use advanced eases like Elastic, Bounce, RoughEase, SlowMo, etc.
  4. You lose compatibility with older browsers like IE9 and earlier. 
  5. You cannot animate along a curve or do physics-based motion or tap into any of the other advanced features that GSAP offers. 
  6. It won't work for SVG very well. GSAP solves a lot of problems, like: http://css-tricks.com/svg-animation-on-css-transforms/
  7. You cannot independently control transform components like rotation, scale, skew, and position with different timing and/or eases. It's simply impossible with CSS. See http://greensock.com/css-performancetoo. In my opinion, this alone is a deal-breaker in many cases, but it depends on the type of animation you're doing. 

I'm not saying it's BAD to use CSS in this case. But beware of the tradeoffs. The other X-factor is that you'd hate to put a bunch of time building out your animations in a less-capable system like CSS, thinking it suits your current needs, and then you discover that you actually do need something that's impossible with CSS. It's a very awkward spot to be in, because you'd need to back up and rebuild stuff in a different system, scrapping some of your work or (perhaps worse yet) ending up with a hybrid approach where certain pieces are in GSAP and certain pieces are in CSS and you cross your fingers hoping you don't run into spots where they overlap or need to interact/integrate. As far as I can tell, GSAP can do everything CSS can do, plus a ton more. So if you start with the more capable system, you'd never run into that situation, but if you start with CSS, you may. It really depends on your project and requirements. 

 

If I were you, I'd probably run some tests and see if the performance difference between the jQuery/CSS className swap and the GSAP one is noticeable at all to the naked eye. Sometimes it's easy to spend way too much time focused on intricate benchmarks that don't reflect reality at all. If the jQuery/CSS solution is indeed noticeably faster in this case and you don't need the extra features GSAP offers, I'd go that direction but use GSAP for the other pieces. Like I said, className tweens are a very unique bird in terms of being costly performance-wise. 

 

Does that help?

  • Like 3
Link to comment
Share on other sites

Thanks Carl, you considerations are very helpful and for the most part they match with what I've been thinking. My usual approach to things (not only in web-development) is to build everything as flexible as possible from beginning, so in the case I want to change or elaborate something later (which often happens) I wouldn't have to do everything from scratch. From this point of view, certainly, GSAP wins the competition.
Can you propose some kind of valid test to compare JQuery/CSS vs GSAP performance in custom project? Because i see that I haven't accounted for everything when I tried to do it with console.time

Link to comment
Share on other sites

Oh, gosh, if you're asking how to test if jQuery is faster than GSAP, there's no question. GSAP blows the doors off of jQuery for animation. It's literally up to 20x faster. http://greensock.com/js/speed.html

 

Like I said, className is a very special case, and jQuery can't even do that anyway - you'd only be using jQuery to swap a class name and then using CSS transitions to do the actual animation. As I explained, that would be faster in most cases. But for pretty much everything else (other than className), GSAP is much faster. It's not even close. 

 

As far as a valid test, I'd suggest your eyes mostly :) Or take a scenario in your particular app where you'll be doing important animation, and then stress-test it by doing a LOT of that same thing at the same time and watch it on your computer, a mobile device, etc. The most important thing in most cases is how it actually looks to the end user anyway. Make sense?

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