Jump to content
Search Community

Calling "TweenMax.set" before animating elements

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

I've noticed from the homepage demo that one often needs to manually set properties on elements before animating them:

// create timeline
    var tl = new TimelineMax();

// set properties
    TweenMax.set(el, {autoAlpha:0, left:0});

// animate
    tl
        .to(el, 1, {autoAlpha:1})
        .to(el, 5, {left:100});

Am I correct in thinking that this is to set reference keys so that the object will animate correctly when animated by other timelines?

 

I'm a bit woolly on this one to be honest, but my testing, and looking at the homepage animation confirms this to be so, at least some of the time..

 

I've also noticed that just setting the properties using the Timeline instance alone (and not separately, via TweenMax) doesn't always work. I think this has to do with when set() is the first item in the timeline, it fails. Adding anything before allows it to succeed, so I seem to be adding lots of needless code to let this happen. Is that correct?

 
If someone could explain exactly why we need to do this, and what's happening conceptually or even practically with a Pen, that would be great.
 
Cheers,
Dave
  • Like 1
Link to comment
Share on other sites

No no, it's not necessary for "reference keys" or anything like that - I just used set() to make the element render at that spot initially (rather than editing the CSS or something - TweenMax handles the cross-browser and prefixing needs, so it's just easier). 

 

You said that setting values in a timeline doesn't consistently work (particularly when it's the first tween) - can you please provide an example? I've never seen that to be the case, and I just tested it and it seemed to work as expected.

 

Keep in mind, however, that if you put a set() into a timeline such that it's scheduled to happen in the future, it SHOULDN'T set it immediately. In other words, timeline.set(element, {x:100}, 1) should leave element.x as-is and then in 1 second, it'll jump to 100 instantly (no tweening). 

 

Could you provide a simple codepen that shows it not working as you'd expect?

Link to comment
Share on other sites

Hi Jack,

 

OK, check this out:

 

See the Pen keJpE?editors=101 by davestewart (@davestewart) on CodePen

 

It's an animation for a company explaining what they do, and is based around various locations such as "street", "store", "bank", etc. which we will revisit at various points in the story.

 

Here's the (technical) low down on the above test PEN, which is a less complex version of how I plan to animate the final version:

  • There are 3 main HTML elements (locations)
  • Each location will contain various child elements (sprites)
  • To animate these locations and sprites there are 6 TimelineMax instances (scenes)
  • Each scene (Timeline) does the following:
    • Globally-increments and sets the z-index of the location element (so it will appear over the previous location element, so it can fade in)
    • animates the location element autoAlpha from 0 to 1
    • animates the sprite element .left from 0px to 200px
  • Each scene will be added to a global root Timeline

Animation-wise, it looks somewhat like this (numbers are alpha, the reversed-order what the the z-index animation should result in):

root timeline   : ----------------------------------------------->

scene 6, bank   :                                         0--1--->
scene 5, store  :                                 0--1--->
scene 4, street :                         0--1--->

scene 3, bank   :                 0--1--->
scene 2, store  :         0--1--->
scene 1, street : 0--1--->

I've added various .set()s to the child timelines to reset the various animated properties when the timeline starts.

 

However, I'm getting really unreliable results on the 2nd set of timelines, which animate the previously-animated locations and sprites:

  • On the first play of the root timeline, there's no visible update from scene 4 onwards (I'm pretty sure this is a location z-index issue, where scene 3's office is not allowing scene 4's street to show)
  • On the second play-through and subsequently, on the last 3 scenes, the smooth .to() acts like an abrupt .set() (I don't know why this is - feels like a bug)

 

I just don't understand what's happening here, or why I can't seem to-reanimate previously-animated properties.

 

Or are the sub timelines animating the same elements conflicting somehow? I would have thought if one had finished animating, a new timeline could take over the animation.

 

In the final animation, there are 4 locations (HTML) and 11 scenes (Timelines), each of which will control multiple sprites within each location. My worry is that this approach of re-animating content just doesn't work.

 

Would really appreciate some input!

 

Thanks,

Dave

Link to comment
Share on other sites

Hi Dave,

 

There are some pointers for your code, particularly this part:

var tlMain = new TimelineMax({name:'main'});

// preemptively set all timelines to 0 z-index and 0 alpha
  TweenMax.set($('.location'), {zIndex:0, autoAlpha:0})

// animation, round 1
  tlMain.add(timeline('#street'));
  tlMain.add(timeline('#store'));
  tlMain.add(timeline('#bank'));

// reset all animation
  TweenMax.set($('.location'), {zIndex:0, autoAlpha:0})
  TweenMax.set($('.location span'), {left:10})

// animation, round 2
  tlMain.add(timeline('#street'));
  tlMain.add(timeline('#store'));
  tlMain.add(timeline('#bank'));

First, all GSAP methods returns the instance being used, in this case tlMain, so they are chainable, in order to save the developers some typing time. Then you're putting between the two rounds of the animation some set instances. Those two will take effect as the code executes but not when the parent timeline runs. The correct way to set the values is to put those instances in the parent timeline as well, like this:

tlMain
   .add(timeline('#street'))
   .add(timeline('#store'))
   .add(timeline('#bank'))
   .set('.location', {zIndex:0,autoAlpha:0})
   .set('.location span', {left:10})
   .add(timeline('#street'))
   .add(timeline('#store'))
   .add(timeline('#bank'));

Now after the first round of timelines is played the opacity and z-index are returned to the starting values.

 

Finally GSAP has a small selector engine built in, so you don't need to pass the jQuery function, GSAP selects the element for you whether by class or ID. As you can see your code is now shorter and easier to read and understand it's progression.

 

I've forked your pen:

 

See the Pen GtHBI?editors=001 by rhernando (@rhernando) on CodePen

 

Rodrigo.

Link to comment
Share on other sites

Hi Rodrigo,

 

Thanks, but simply resetting ALL .location elements halfway through the main animation isn't my goal. That was just a hacky alternative attempt (to see if it made any difference) to work around GS not resetting properties within the individual timeline() method, before returning a new Timeline instance.

 

Think perhaps I will delete that Pen if it's confusing the issue!

 

Check out the original Pen (cited at the top) to see what I want to achieve:

 

See the Pen keJpE?editors=101 by davestewart (@davestewart) on CodePen


 

I want each timeline() method (there will be variations on this) to be responsible for resetting its own elements:

 

FYI, the methods also aren't chained in this example, as in the final version of the code each child Timeline will be added to the main Story timeline via dependency-injection, so I didn't want to imply that it would all be done in one place:

 

2dYV0FU.png

 

Cheers,

Dave

Link to comment
Share on other sites

Let me shed a little more light on what's going on here...

 

Short answer: set immediateRender:false on your timeline.set() calls. Or, alternatively, you could position those set() calls anywhere except 0 on the timeline (even 0.00000001 seconds into it)
 

Longer explanation:

Normal tweens should not render immediately because it's utterly wasteful. Imagine setting up a tween to animate element.x from 0 to 100. When you create the tween, its progress would be 0 anyway, so nothing has changed, thus it's wasteful to burn CPU cycles setting the values to what they already are. Therefore, by default, tweens always render on the next "tick" (meaning some time has elapsed, thus change occurred). 

 

Also keep in mind that a tween records starting values when it renders for the first time. Otherwise, there are a host of other problems you'd run into (I'll spare you the details unless you request them). Plus it's a performance optimization.

 

set() calls are a bit of an odd bird in that they're just zero-duration tweens, yet they're not technically "tweens" at all because there are no inbetween values. And yet they are tweens because they have to record before/after state information and be able to be nested in timelines and render properly when the playhead moves forward/backward over them. 

 

Note: you can always control whether or not a tween renders immediately by setting immediateRender:true or immediateRender:false. Otherwise, the engine applies what it thinks is the most logical option for you. 

 

Normally, people want set() "tweens" to immediately render. For example:

var obj = {x:0};
TweenLite.set(obj, {x:100});
console.log(obj.x); //100

But if there is any delay on the tween, it shouldn't render immediately:

var obj = {x:0};
TweenLite.set(obj, {x:100, delay:0.1});
console.log(obj.x); //0

Okay, hopefully this is all pretty obvious and intuitive. Now let's talk timelines...

 

What should happen if someone does this?:

var tl = new TimelineLite();
var obj = {x:0};
tl.add(TweenLite.set(obj, {x:100}), 1);
console.log(obj.x); //should it be 0 or 100? In this case it's 100

See the problem? When the TweenLite.set() is called, it returns a zero-duration tween that has no idea what you're going to do with it. In this case, you're immediately dropping it into a timeline and scheduling it not to run until 1 second into the timeline! So when it's created, it assumes it should render immediately (because it doesn't know about the timeline scheduling you'll do after the fact). 

 

One of the nice things about the convenience methods of TimelineLite and TimelineMax is they allow us to better guess what you're trying to do. So if you used the timeline's set() method instead of the longer syntax where you create a TweenLite.set() and then add() it into the timeline, you do this:

var tl = new TimelineLite();
var obj = {x:0};
tl.set(obj, {x:100}, 1); 
console.log(obj.x); //0 because the timeline saw that you're placing the set() later in the timeline (not at zero), thus it can set immediateRender:false for you. 

In your case, you were actually placing your set() calls at the very beginning of your timelines (time:0), thus it thought you wanted them rendered immediately (after all, time:0 means "now" in this case), but then you're returning some of those timelines and dropping them into a master timeline such that they're scheduled for later, thus you don't really want those to render immediately. The set() still sits at time:0 on the nested timelines, of course, but the timelines themselves are pushed later on a master timeline. There's no way for the engine to anticipate that you're going to do that. 

 

So again, since you know that you're scheduling this stuff to render later, you can just manually set immediateRender:false on those set() calls. Note that this ONLY matters for set() calls that you're putting at the VERY beginning of a timeline which you'll be setting to run later. If you positioned those tweens even 0.00000001 into the timeline, they'd automatically have set immediateRender:false for you anyway. So you just happened to stumble upon an edge case where it's necessary/helpful to manually set immediateRender:false. In 99.9% of the cases, the engine just handles it in the most intuitive way for you - it just had no way of doing so in this case (it'd be logically impossible as far as I can tell). 

 

Does that clear things up? 

  • Like 4
Link to comment
Share on other sites

Hi Dave,

 

The issue there is that when the second run starts all the opacity values of the slides are 1, so basically GSAP records those values (for optimization purposes) and tweens them to the end values. So basically what happens is that the values are being tweened from 1 to 1, therefore there's no fade.

 

One option is use a fromTo() instance that will always take the starting value to the one you need:

var tl = new TimelineLite();

tl.fromTo(element, 1, {autoAlpha:0}, {autoAlpha:1});

Another alternative is to add some code at the end of the nested timelines in order to create a cross-fade effect between both slides, something like this:

function timeline(id)
{
// variables
  ++zIndex;
  var slide = id;
  var title = $(id).find('span');
  var name = 'scene ' + zIndex + ' (' +id.replace('#', '')+ ')';
  var props = {name:name, onStart:onStart};
  var tl = new TimelineLite(props);
    
  // set main timeline label
  tlMain.addLabel(name);
    
  // animate
  tl
  //reset title
  .set(title, {left:10})
      
  // fade in
  .to(slide, 0.5, {autoAlpha:1})
    
  // animate title
  .to(title, 1, {left:200})
          
  .to(slide, 0.5, {autoAlpha:0})
      	 
  .set(title, {left:10});
      
  // return
  return tl;
}

// variables
var zIndex = 0;
var tlMain = new TimelineMax({name:'main'});

// animation, round 1
  tlMain.add(timeline('#street'));
  tlMain.add(timeline('#store'), "-=0.5");
  tlMain.add(timeline('#bank'), "-=0.5");

// animation, round 2
  tlMain.add(timeline('#street'), "-=0.5");
  tlMain.add(timeline('#store'), "-=0.5");
  tlMain.add(timeline('#bank'), "-=0.5");

I've updated the pen:

 

See the Pen GtHBI?editors=001 by rhernando (@rhernando) on CodePen

 

Rodrigo.

Link to comment
Share on other sites

Jack, I'm so happy I could cry!!!

 

I tried that and it now works perfectly.

 

There's been a lot of set up for this, and I was beginning to wonder if it was all going to be a big fat waste of time. I'm so relieved, I could actually relieve myself.

 

I'll have to read through your very informative post a couple of times, but I get it, in general.

 

With regards to the edge case / logic condition - what about the possibility of skipping the optimization of .set() calls if they're at 0.0 in a Timeline?

 

I seems to me that adding a few cycles to an animation when it starts is going to add a tiny, tiny fraction of the overall cycles that are needed for the whole thing. And if it's a top-level timeline, it wouldn't matter anyway, but if it's a nested timeline, it could be mission (rather than performance) critical, as you say, there's no way for a parent-agnostic Timeline to detect if it's nested or not.

 

It's just a thought anyway - obviously I don't really understand what's fully behind that statement as I didn't write the engine.

 

So I'm going to go and sob silently for a bit, then crack on with the rest of this when I've done that. Though I might have a beer first (it's Friday night here).

And Rodrigo, thanks for putting some more time into providing a workaround too.

All the best for now,

Dave :D

  • Like 1
Link to comment
Share on other sites

With regards to the edge case / logic condition - what about the possibility of skipping the optimization of .set() calls if they're at 0.0 in a Timeline?

 

I seems to me that adding a few cycles to an animation when it starts is going to add a tiny, tiny fraction of the overall cycles that are needed for the whole thing. And if it's a top-level timeline, it wouldn't matter anyway, but if it's a nested timeline, it could be mission (rather than performance) critical, as you say, there's no way for a parent-agnostic Timeline to detect if it's nested or not.

 

Are you suggesting that timeline.set() calls would always have immediateRender:false even if they're at a time of 0? If so, imagine this [relatively common] scenario:

var tl = new TimelineLite();
tl.set(element, {opacity:0})
  .to(otherElement, 1, {x:100})
  .to(element, 1, {opacity:1});

If immediateRender was false on that set() call, users would see the element flash on the screen briefly (for like 1/60th of a second) because it wouldn't render as opacity:0 until the next tick. 

 

See the dilemma? 

 

I can't see any way of implementing logic that would perfectly handle every situation, so it becomes more about making the default be the most intuitive for the most common scenario(s). I'm on the fence about just always making immediateRender:false on set() calls in timelines. Anybody else want to weigh in? One of the biggest things that scares me is if people have legacy projects that depend on the current functionality and then they update and suddenly they see that brief flash/unrendered state at the beginning. Then again, timelines are awesome for nesting so to avoid problems like Dave ran into, it might be a worthwhile sacrifice/risk. Yet it only happens when the set() is exactly at zero. Definitely an edge case it seems.

 

And Dave, I'm glad to hear you're so relieved, but then you said you're going to go sob for a while. Are you feeling extra bipolar today? :) Why the sobs? I thought it was good news. 

Link to comment
Share on other sites

Tears of relief and joy Jack, tears of relief and joy!

 

The whole project has been a lot more complicated than I thought when I took it on. I'm a week behind an already tight turnaround of 5 days, with a lot of unknowns and WTFs for myself, which translated into stress and heavy expectation management for my client, and their client.

 

I hate to speak too soon, but I think this is the last hurdle vaulted...!

 

In regards to your point - I totally see your point there, and don't think you should change either.

 

However, what about a new .reset() method? It would work the same as set(), yet be explicit-enough to do exactly what it says on the tin.

I've already added a bunch of methods to the TweenMax and TimelineMax prototypes, one more won't hurt!

TweenMax.zIndex = 1;

var methods =
{
	reset:function(element, props)
	{
		return this.set(element, $.extend(props, {immediateRender:false}));
	},
	
	show:function(element)
	{
		return this.set(element, {autoAlpha:1})
	},
	
	hide:function(element)
	{
		return this.set(element, {autoAlpha:0})
	},
	
	fadeIn: function(element, time, props)
	{
		return this.to(element, time || 0.5, $.extend(props, {autoAlpha:1}));
	},
	
	fadeOut: function(element, time, props)
	{
		return this.to(element, time || 0.5, $.extend(props, {autoAlpha:0}));
	},
	
	wait: function(time)
	{
		return this.to({}, time || 1, {wait:0});
	},
	
	raise: function(element)
	{
		return this.set(element, {zIndex:++TweenMax.zIndex});
	}
	
};

$.extend(TweenMax.prototype, methods);
$.extend(TimelineMax.prototype, methods);

 

Cheers,

Dave

Link to comment
Share on other sites

Ah, glad to hear they were tears of joy! You had me worried there for a moment. 

 

I don't think reset() is intuitive, since it implies going back to some sort of default/original values. I think it's best for us to just allow people to set immediateRender in their set() calls at this point (it keeps the API cleaner too), but don't hesitate to make other suggestions in the future. Some of the best ideas come from users in the trenches like yourself. 

 

Good luck with the project. Sorry to hear it has been so much more complex than you anticipated. I was hoping to hear that the GreenSock tools made it much easier/faster to accomplish what you're after, but perhaps it was just a matter of pushing through the learning curve and the next project you tackle, you'll be like a ninja with GSAP and it'll be a cake-walk to do whatever animation you want :)

Link to comment
Share on other sites

There was certainly a learning curve with timelines and how best to put them together. I fell into so many traps it was unbelievable!

 

Coming from an ActionScript background as well, even though I write oooodles of JavaScript, and have done for years, I was trying to shoehorn a lot of stuff into Prototypical classes too, when I think perhaps JS / GSAP timeline animation is easier to setup when pursuing a more functional approach.

 

There was also a lot of decisions to be made about structure, elements, declarative vs programatic instantiation, non-timeline related animation, spritesheets, not-spritesheets, GIFs vs PNGs, interactivity, to utilise the structure and support of a library like Backbone to organise the application, to use jQuery widgets to manage states... the list goes on and on, and in fact GSAP was only one component of it all, albeit an extremely important one, and one without which the fully-animated story-telling would no doubt be impossible.

 

I'm going to write a blog post about the journey though all these different areas when done, and try and distill some of the learning (of which there's been a LOT) for this type of project.

Interestingly, still no one has replied to my preemptive post regarding any existing animation scene managers. Ironically, perhaps I will be the first to answer my own post!

So, that's it for now. Thanks loads for your support. It's been invaluable.

Cheers,

Dave

Link to comment
Share on other sites

Sorry about neglecting that previous post where you asked for any input on tools/structure for doing your project. It slipped through the cracks (not that I had anything valuable to offer). 

 

I think it'd be awesome if you did a blog post detailing some of what you learned, which tools were useful and why, etc. Of course I assume it would give a glowing review of GSAP ;)

 

Please let us know when you put that out there. And we'd love to have you sticking around in the forums and helping others with all the knowledge you've gained. Seriously, we need more people like you around here. Carl, Rodrigo, Jonathan, Crysto, and Jamie are rocks stars in the forums and we're always looking for new blood...I mean...sharp developers who want to help others make the best use of GSAP tools and build awesome sites. 

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