Jump to content

Search In
  • More options...
Find results that contain...
Find results in...

Inconsistent behavior

Recommended Posts



I ran into some odd behavior with TimelineMax recently.


When setting 'display:none' in the timeline, and running it in reverse, everything works as expected - most of the time.


Quite often though (it's completely random), the display property does not get set - or reset if you will - when reversing, leaving it at 'display:inherit' after the animation is done, as demonstrated in the fiddle. You need to open the debugger to see it (and click the button until it occurs).




Adding 'clearProps:display' at the start of the timeline is causing other issues, such as 'display:inherit' not being set when expanding.


The behavior is consistent in both Chrome and Firefox.


Any ideas?

Link to comment
Share on other sites

After multiple tries I didn't notice any odd behavior in Chrome or Firefox.


Following video explains how from tweens work, so it will explain some of the behavior.



  • Like 1
Link to comment
Share on other sites

Thank you for taking the time to look at it, Sahil.


I fail to see what immediateRender has to do with this particular issue though...?


I edited the fiddle so you don't have to continously press the button. It will show a popup when there's a mismatch. Sometimes it runs for 20+ cycles, but it always fails at some point.



  • Like 1
Link to comment
Share on other sites

Hi lennco,


Welcome to the forums. Thanks for the demo.

Sorry to hear you hit a snag. 


I was able to confirm (via watching the elements in dev tools) that when you reverse to the beginning, the display value is sometimes staying on inherit and not changing back to display:none. Although it is rare, there is an inconsistency we have to look into further.


Putting a set() at the beginning of a timeline (time(0)) is very tricky because a set() is a tween with no duration. Since it has no duration it has to technically complete as soon as it is created on first run. And even though it has no duration (the start time and end times are both 0) it still has to record a start value and end value, or in your case a "before the timeline plays forward values" and a "after the timeline plays forward value".


Where things get super tricky is when you consider what happens when the timeline is reversed? When you reverse back to a time of 0 should the value you set() at a time of 0 be rendered (in your case:display:inherit)? Or the value that existed BEFORE the timeline was played be rendered (display:none). 


Although it could be argued both ways it appeared the more desired behavior was that if you REVERSE back to 0 then the user would want to see things as they were BEFORE the timeline first played. There is logic in the engine that detects this condition and will render the value before the "set" occurred. That is why you see things mostly working correctly when display:none is set after reversing.


However, their is also logic in place that accounts for the situation where a timeline has played forward and then the user wants to do pause(0).

What should happen then? At a time of 0 you are specifically using a set to set a value so at a time of 0 in this case we render the "end / completed" state of the set.


I forked your demo to snap the menu close by using pause(0) instead of reverse(). If you look at dev tools you will see that the inline style stays as display:inherit because at a time of 0 you have a set that dictates that must happen at a time of 0.


You can see that behavior here: https://jsfiddle.net/wp7qb448/


All that to say, dealing with 0-duration sets() at the beginning of a timeline is very difficult under the hood. 


As we look into this further, there is a solution that I believe will give you the desired results.

Don't put your set at a time of 0, add it just a little bit into the future like:


tl.set(allSub, { display:'inherit' }, 0.0001);


This will ensure that when you reverse the playhead always goes backwards beyond this point and will render the beginning state of the set (display:none)


See it here:






  • Like 6
  • Thanks 1
Link to comment
Share on other sites

Hello @lennco .. To add to the Mighty Carl's great advise. Great explanation @Carl !


I could not replicate what you were seeing even after clicking the "main" text like a crazy person to show and hide the elements.


But here is my two cents.. Why not just use autoAlpha along with the CSS display property on the same tween. GSAP is smart enough to set display none at the end of the tween after autoAlpha animates to 0. And then when animating to show the element GSAP is smart enough when animating  autoAlpha to 1,  to set display to block.  But in order for this to work GSAP must have both autoAlpha and display set on the same tween.




So to recap... GSAP will automatically set the CSS display property to none or block once autoAlpha reaches 0 (sets to display:none) or 1 (sets to display:block). I also commented out display:none from the CSS rule .sub and made that visibility: hidden for use with the GSAP autoAlpha special property. This way you know GSAP will always set the display property after transitioning autoAlpha form 0 to 1 or from 1 to 0.


var allSub = document.querySelectorAll('.sub');
var allSubText = document.querySelectorAll('.sub > span');
var tl = new TimelineMax();
var isOpen = false;
//tl.set(allSub, { clearProps:'all' });
//tl.set(allSub, { display:'inherit' });
tl.staggerFrom(allSub, 0.3, { autoAlpha:0, display:'none', borderWidth:0, height:0, marginTop:0 }, 0.05);
tl.staggerFrom(allSubText, 0.3, { autoAlpha:0, display:"none" }, 0.05, '-=0.3');


autoAlpha is part of the GSAP CSSPlugin :




Does that help? :) 

  • Like 5
  • Thanks 1
Link to comment
Share on other sites

Thank you for the very thorough explenation and clear examples, Carl. Much appreciated.


Your sugested solution also works well.

Link to comment
Share on other sites

Hi Jonathan,


I only tried clicking the button like a normal person, so maybe that's why you can't replicate the problem? :-P


In either case, your solution works very well. Best of all: it doesn't require a "hack". So I think that's the one I'll go with.


Where did you find the information on GSAP setting the display property at the end of the animation when using autoAlpha? The documentation only mention that it sets the visibility property. The few times I've tried using the display property in timed tweens, I never ended up with the desired result...

  • Like 1
Link to comment
Share on other sites

Hello again @lennco and welcome to the GreenSock forum!


Its not documented in the GSAP Docs, but it is found in various places in the forum. But IMHO using GSAP autoAlpha with CSS display property can be better than using autoAlpha by itself if you need to show hide stuff. Since in CSS an element with just visibility:hidden and opacity:0 can still take up space and can still be clickable. So having display none on it can be very useful.


Happy Tweening :)

Link to comment
Share on other sites

Just to clarify why this happens intermittently (and sorry if this is long-winded, but it's tough to explain concisely)....


All animation engines update their values at certain increments, although those increments may vary based on many, many factors. Ideally it'll be 60 times per second (every 16.667ms), but there's always some variance. Maybe the CPU is busy doing something else, so it's 18ms instead of 16.6 (or whatever). So in your case, you put a zero-duration tween at the VERY START of a timeline which is precarious as Carl explained. The playhead gets repositioned each time the engine updates (roughly 60x per second), so when you reverse() and the playhead eventually reaches the start of the timeline, it's very unlikely that it'll land EXACTLY on top of that time (0). More likely it'll be a tad on either side (like -0.012 or 0.01 or whatever). 


If the playhead goes backward BEYOND the start, there's special logic for the zero-duration tween that'll make it render at the value BEFORE the set. But if the playhead lands exactly on top of that time (0 in this case), it'll of course set the value to whatever you set() it to there. And the time cannot go negative in a timeline, so it stays there and it also makes the timeline eligible for garbage collection (to clear memory). 


You just happened to run into the perfect conditions to expose this logic conundrum. It's not really a bug - it's just an awkward logic condition that's easily solved by not putting a zero-duration tween at the VERY beginning of a timeline that you're going to reverse(). If you had a non-zero duration or if you positioned it even at 0.000001 into the timeline, it'll work perfectly every time. 


Hopefully that helps shed light on what might initially seem like weird/buggy behavior. 


Happy tweening!

  • Like 3
Link to comment
Share on other sites

More detailed explenations as to why are always appreciated, so thank you!


And it all makes perfect sense after reading your post.

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