Jump to content
Search Community

How adding to a timeline works/executes

NickWoodward test
Moderator Tag

Recommended Posts

Evening all!

I feel like I'm definitely getting better with my animations/timelines, but I've a few simple questions if anyone minds taking a look?

I've got a timeline in the global scope which I add to in an `init()` function (lines 10 & 22 to 26 in my codepen):
 

// init function

  // Line 10   
  mainTl.add(loaderInAnimation());
      

  // Lines 22 - 26     
  mainTl    
    .add(loaderOutAnimation())     
    .add(() => firstRender())
    .add(() => contentInAnimation())
    .add(() => addListeners()) 

I'm just a bit confused about how a timeline executes - and the differences between adding a nested timeline in a function that executes immediately (eg: loaderInAnimation & loaderOutAnimation) and one that's called in an anonymous function.

The only difference I *think* I understand (in the context of timelines) is that if an animation wants to select an element that's not yet in the DOM, then I need to use an anonymous function (eg: contentInAnimation requires an element added by firstRender)

But there must be more to it than that, because changing () => firstRender() tofirstRender()changes the behaviour in a way I don't expect that breaks the animation too.


Another (probably related) example of my confusion about how timelines work is on line 66+
 

// This is inside a click event handler on line 66
  paginationTl   
    .add(contentOutAnimation())   
    .add(loaderInAnimation());

If this timeline is also in the global scope, but the code above is executed after a click event, why isn't it repeatedly adding both animations everytime a click event occurs?


Sorry for the long question, and the long codepen - I'm just trying to get a better understanding of timelines by making a more practical demo. I know it's not much, but I'm pretty happy it works 😊 (But feel free to rip it apart - It still feels like I'm doing things wrong!)

Thanks,

Nick
 

See the Pen QWOBjYj by nwoodward (@nwoodward) on CodePen

Link to comment
Share on other sites

  • NickWoodward changed the title to How adding to a timeline works/executes

Hi Nick,

 

GSAP is not executing a function here. You executed it by writing it like that. GSAP is just adding whatever that function returns.

.add(loaderOutAnimation())     

 

It would be the same as putting it in a variable.

 

let myAnimation = loaderOutAnimation();

tl.add(myAnimation)  

 

Or if you want to get messy, just add the animation directly.

 

tl.add(gsap.to(".foo", { x: 100 }))

 

All this other stuff is just functions. It doesn't matter if any of those functions return an animation, it will not be added to the timeline.

    .add(() => firstRender())
    .add(() => contentInAnimation())
    .add(() => addListeners()) 

 

And you really don't need to use an arrow function.

    .add(firstRender)
    .add(contentInAnimation)
    .add(addListeners) 

 

Here's an old post, that might clear some of that up. It's using the older v1/v2 syntax, but hopefully it will makes sense.

 

 

34 minutes ago, NickWoodward said:
// This is inside a click event handler on line 66
  paginationTl   
    .add(contentOutAnimation())   
    .add(loaderInAnimation());

If this timeline is also in the global scope, but the code above is executed after a click event, why isn't it repeatedly adding both animations everytime a click event occurs?

 

It is. I didn't follow the paper trail of what those animations are doing, but you're just adding the same stuff over and over again to the timeline. It's very easy to verify after clicking.

 

console.log("children", paginationTl.getChildren())

 

Link to comment
Share on other sites

Hi @OSUblake, thanks for the quick reply!
 

1 hour ago, OSUblake said:

GSAP is not executing a function here. You executed it by writing it like that. GSAP is just adding whatever that function returns.

.add(loaderOutAnimation())     

Yeah, that's what I had thought. So that's adding the returned timeline correctly. And you're saying that this:
 

1 hour ago, OSUblake said:

All this other stuff is just functions. It doesn't matter if any of those functions return an animation, it will not be added to the timeline.

    .add(() => firstRender())
    .add(() => contentInAnimation())
    .add(() => addListeners()) 

in particular the contentInAnimation() isn't being added to the timeline as I expect - it's just being added as a function/callback that then runs in sequence?

So let's say I make the contentInAnimation() duration really long, and add another animation after it (adding the loaders back in for whatever reason):

 

.add(() => firstRender())
.add(() => contentInAnimation())
.add(() => renderLoaders())
.add(loaderAnimationIn())
.add(() => addListeners()) 

loaderAnimationIn() won't work as expected because as it's not part of the TL it won't wait for the contentInAnimation() (and its long duration) to finish, right?
 

 

1 hour ago, OSUblake said:

Here's an old post, that might clear some of that up. It's using the older v1/v2 syntax, but hopefully it will makes sense.

I'm not going to lie, I didn't really understand his use case, but I did understand your reply to him (I think).

So basically:
 

// Callbacks, not part of the timeline, but do run sequentially (won't wait for animations to end)
.add(() => animation())
.add(animation)

// Timelines / tweens (depending on return value), added to the timeline
.add(animation())

Is there any difference between these functions: .add(notAnAnimation) and .add(notAnAnimation()) other than the latter is added to the timeline and would therefore wait for the end of any previous animation?
 

1 hour ago, OSUblake said:

It is. I didn't follow the paper trail of what those animations are doing, but you're just adding the same stuff over and over again to the timeline. It's very easy to verify after clicking.

Yup, I can see that now. Useful method, thanks. Is there any way to see the element that each child animation applies to? It wasn't immediately obvious to me (I was just interested because I had 11 objects, but only about 6 animations I thought I'd added)


Next step: Trying to apply my new found knowledge to that codepen. Thanks a lot, really appreciate it. 🙂

Nick

Link to comment
Share on other sites

Worked perfectly, thanks.

I do still have the pagination problem - would reinitialising the timeline (line 2) be a legit approach? It appears to work.
 

    item.addEventListener('click', (e) => {

      // paginationTl = gsap.timeline();

      paginationTl
        .add(contentOutAnimation())
        .add(loaderInAnimation());
      
      // Simulate longer response
      setTimeout(async () => {
        try {
          // Update data
          response = await getToDos(index+1);
          todos = response.data;
          
          paginationTl
            .add(loaderOutAnimation())
            .add(updateView())
            .add(contentInAnimation());
          
          
            console.log("children", paginationTl.getChildren())
        } catch(err) {
          console.log(err);
        }
      }, 2000);
    });

 

Link to comment
Share on other sites

9 minutes ago, NickWoodward said:

Is there any difference between these functions: .add(notAnAnimation) and .add(notAnAnimation()) other than the latter is added to the timeline and would therefore wait for the end of any previous animation?

 

Whatever code you have in that function will run at different times, the first will run whenever it's supposed to according to it's position in the timeline. The latter will run immediately because you are executing the function.

 

 

Link to comment
Share on other sites

6 minutes ago, OSUblake said:

 

Whatever code you have in that function will run at different times, the first will run whenever it's supposed to according to it's position in the timeline. The latter will run immediately because you are executing the function.

 

 

But I thought the former wouldn't be added to the timeline correctly?

 

1 hour ago, OSUblake said:

All this other stuff is just functions. It doesn't matter if any of those functions return an animation, it will not be added to the timeline.

    .add(() => firstRender())
    .add(() => contentInAnimation())
    .add(() => addListeners()) 

 

 

Link to comment
Share on other sites

1 minute ago, NickWoodward said:

I do still have the pagination problem - would reinitialising the timeline (line 2) be a legit approach? It appears to work.

 

Most likely. If you're adding stuff inside an event handler to a timeline created outside of that handler, you're probably doing it wrong. There might be cases where you want to do that, but usually not.  😉

 

 

Link to comment
Share on other sites

1 minute ago, NickWoodward said:

But I thought the former won't be added to the timeline correctly?

 

What I mean is that the callback will be called at the time it appears in the timeline. If you try to return an animation in that callback, the timeline will not wait for that animation. It's just calling a function. Nothing more. The timeline has no concept of what's running inside that callback. 

 

Start out with something simpler to understand it better. The timeline will not wait animateBox1 animation before starting the box2 animation.

 

See the Pen bGYQJBO by GreenSock (@GreenSock) on CodePen

 

Link to comment
Share on other sites

17 minutes ago, OSUblake said:
34 minutes ago, NickWoodward said:

Is there any difference between these functions: .add(notAnAnimation) and .add(notAnAnimation()) other than the latter is added to the timeline and would therefore wait for the end of any previous animation?

 

Whatever code you have in that function will run at different times, the first will run whenever it's supposed to according to it's position in the timeline. The latter will run immediately because you are executing the function.

 

4 minutes ago, OSUblake said:

What I mean is that the callback will be called at the time it appears in the timeline. If you try to return an animation in that callback, the timeline will not wait for that animation. It's just calling a function. Nothing more. The timeline has no concept of what's running inside that callback. 


Yup, that's what I understood from the post you linked. Amazing 😊

  • Like 1
Link to comment
Share on other sites

Hi @OSUblake

Found myself a little confused again with the difference between the two as I tried to implement this in my project. Sorry!

Made a simpler codepen here:

See the Pen bGYZWRJ?editors=1011 by nwoodward (@nwoodward) on CodePen



You said that a callback will be called at the time it appears in the timeline, and that if it returns an *animation* it won't be waited for, which makes sense.

But then why won't it wait for the callback itself to finish?
 

function animate() {
  const summaryContent = document.querySelector('.summary-content');
  
  tl
    .to(summaryContent, {autoAlpha: 0})
    // target not found
    .add(renderLoaders)
    // fine
    // .add(renderLoaders())
    .fromTo('.loader', {autoAlpha: 0},{autoAlpha: 1});
}

function renderLoaders() {
  const summary = document.querySelector('.summary');
  const markup = `<div class="loader">Loading...</div>`;
  
  summary.insertAdjacentHTML('afterbegin', markup);
}

Am a little confused why the target is not found if renderLoaders runs instantly and is not asynchronous?

I'm sure it probably relates back to the question I asked here -

Quote

The selector is referencing what's in the DOM at the time you create your animation


But as far as I can see tl.fromTo('.loader', {autoAlpha:0},{autoAlpha:1})is called at runtime in animate()'s execution context, so I don't really understand why it's not being found?

Sorry again for yet more questions!

Nick

Link to comment
Share on other sites

21 hours ago, NickWoodward said:

I'm sure it probably relates back to the question I asked here -

 

Looks like it.

 

21 hours ago, NickWoodward said:

But then why won't it wait for the callback itself to finish?

 

It sounds like you might be misunderstanding how a timeline is created. Each line of code is not evaluated when it's supposed to play in the timeline, it's evaluated as soon as you write it. GSAP sorts out what that should be and adds it to a timeline object. So this is immediately looking for the loader target. If it's found, then it will add that animation to the timeline. 

 

.fromTo('.loader', {autoAlpha: 0},{autoAlpha: 1});

 

Expecting the loader to be found would be no different than writing this. Of course it's not found because the callback hasn't been run.

 

tl.add(renderLoaders)

console.log(document.querySelector(".loader")) // null

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

 

 

  • Like 1
Link to comment
Share on other sites

On 3/6/2022 at 12:33 PM, OSUblake said:

Each line of code is not evaluated when it's supposed to play in the timeline, it's evaluated as soon as you write it.

Yup, it's definitely this line that I'm unsure of, especially the last part, about it being evaluated as soon as I write it.

Is this a distinction between what happens at compile time vs run time?  So the reference to'.loader' is being looked up prior to the animate function even being run?

Or is this my confusion about when the timeline is constructed vs when it actually starts playing (and when the callback is run)?

Sorry if I'm being thick, but I still haven't got my head around it.

Link to comment
Share on other sites

12 minutes ago, NickWoodward said:

Is this a distinction between what happens at compile time vs run time?

 

I don't know the best way to describe all of that and I'd rather not head down that road 🤪

 

15 minutes ago, NickWoodward said:

So the reference to'.loader' is being looked up prior to the animate function even being run?

 

Yes, look back at my "messy" version. 

 

On 3/2/2022 at 1:20 AM, OSUblake said:

Or if you want to get messy, just add the animation directly.

 

tl.add(gsap.to(".foo", { x: 100 }))

 

 

That's all tl.to() is doing under the hood. It's passing all those arguments to create a tween, and then using .add() to add that tween to timeline. Just think of tl.to() as a helper function that simplifies adding tweens to a timeline.

 

Note that I'm using tl.to() interchangeably with all the animation methods like .set(), .from(), and .fromTo(). It works exactly the same with all of them.

 

Would you expect a standalone tween using .loader to work before calling renderLoaders? It's the same principle with a timeline. It creates the tween and adds it to the timeline, and then on next animation tick is when timeline actually starts playing.

 

gsap.fromTo('.loader', {autoAlpha: 0},{autoAlpha: 1});

 

@Carl do you have a lesson that might explain this better than I am?

 

Link to comment
Share on other sites

55 minutes ago, OSUblake said:

I don't know the best way to describe all of that and I'd rather not head down that road 🤪

Haha, I picked up my copy of YDKJS which explains the compiler vs runtime to see if I could work out if it was related to this, and almost instantly put it down again 😄

 

55 minutes ago, OSUblake said:

Yes, look back at my "messy" version. 

 

55 minutes ago, OSUblake said:
tl.add(gsap.to(".foo", { x: 100 }))


Sure, but in my (probably incorrect) mind it feels like the .add() and the .to() are executed within the animate() function, so it doesn't feel (thankfully) like it's a compiler/parsing thing?

How about this as an idea (and this is probably what you initially meant and I just misunderstood):
The first time JS hits this code:

  tl
    .to(summaryContent, {autoAlpha: 0})
    // target not found
    .add(renderLoaders)
    // fine
    // .add(renderLoaders())
    .fromTo('.loader', {autoAlpha: 0},{autoAlpha: 1});

the .to() runs, selects the relevant dom element and adds the animation, the.add() adds the callback (which hasn't run yet but is put in the correct position in the timeline), and the .fromTo() does the same as the .to(), selecting the dom element and adding the animation. The '.loader' element isn't there yet.

The timeline *then* executes itself after the entire timeline is created. Somehow? 🤔

The more I think about it the more I think this seems to make sense to me? Like I said, hopefully this is what you meant and I just misunderstood, because I really don't want it to be about compile vs runtime either! 😄

*Fake edit (because I didn't understand on the first read through)

55 minutes ago, OSUblake said:

Just think of tl.to() as a helper function that simplifies adding tweens to a timeline.

 

55 minutes ago, OSUblake said:

and then on next animation tick is when timeline actually starts playing.

Ah, ok then. So tl.to() tl.add() etc are just like init functions, and aren't executing any of the animations. Got it. That makes sense (I think)

My only problem with this (still) is that I now don't understand how .add(renderLoaders()) fits in (vs the callback). If tl.to()/tl.add() initialise the timeline, and the timeline plays on the next animation tick, doesn't that mean that renderLoaders() isn't added to the timeline at all, and is instead run during the initialisation for the timeline, before it even starts playing?

 

Thanks again!
 

Link to comment
Share on other sites

Going to try and simplify this:

// Using renderLoaders

Line 1: Find summaryContent element in the DOM. Add the animation to the tl
Line 2: Add the callback renderLoaders to the timeline. It's not executed yet.
Line 3: Find '.loader' in the DOM. Add the animation to the tl. This will result in an error because the element isn't there yet.

tl runs on next animation tick, summaryContent animates out, the loader renders instantly and doesn't animate (which is the behaviour I've seen)

3 minutes ago, NickWoodward said:
  tl
    .to(summaryContent, {autoAlpha: 0})
    // target not found
    .add(renderLoaders)
    .fromTo('.loader', {autoAlpha: 0},{autoAlpha: 1});



// Using renderLoaders()
 

tl
    .to(summaryContent, {autoAlpha: 0})
    .add(renderLoaders())
    .fromTo('.loader', {autoAlpha: 0},{autoAlpha: 1});

Line 1: Find summaryContent element in the DOM. Add the animation to the tl
Line 2: Execute renderLoaders() immediately(?). Nothing is added to the timeline(?) and the loader is technically added to the DOM here before the animation runs
Line 3: Find '.loader' in the DOM. Add the animation to the tl. This will work because the element is there already.

tl runs on next animation tick, summaryContent animates out, the loader is already in the DOM prior to the tlrunning, so animates in at the right time.


Hope that's right! 😂

Link to comment
Share on other sites

22 minutes ago, NickWoodward said:

My only problem with this (still) is that I now don't understand how .add(renderLoaders()) fits in (vs the callback). If tl.to()/tl.add() initialise the timeline, and the timeline plays on the next animation tick, doesn't that mean that renderLoaders() isn't added to the timeline at all, and is instead run during the initialisation for the timeline, before it even starts playing?

 

Most of what you wrote above this statement sounds correct.

 

I think you might misunderstanding what renderLoaders() is. That is not a function, and GSAP is not calling renderLoaders() at all. Those parenthesis are executing that function so renderLoaders() becomes whatever that function returns.

 

Try logging out what it is assuming you are using this function.

 

function renderLoaders() {
  const summary = document.querySelector('.summary');
  const markup = `<div class="loader">Loading...</div>`;
  
  summary.insertAdjacentHTML('afterbegin', markup);
}

 

console.log(typeof renderLoaders);
console.log(renderLoaders);

console.log(typeof renderLoaders());
console.log(renderLoaders());

 

You should see something like this.

 

image.png

 

The last 2 are undefined because you aren't returning anything.

 

Now if you did .add(renderLoaders), GSAP does a typeof check and see it's a function, so that means it's going to be placed inside the timeline as a callback.

 

If you do .add(renderLoaders()), you just executed the function, and GSAP see it's undefined, so there is nothing to add the timeline.

 

However, if .add(renderLoaders()) returned an animation (Tween or Timeline), GSAP would detect that and add it to the timeline.

 

 

  • Like 1
Link to comment
Share on other sites

5 minutes ago, NickWoodward said:

// Using renderLoaders()
 

tl
    .to(summaryContent, {autoAlpha: 0})
    .add(renderLoaders())
    .fromTo('.loader', {autoAlpha: 0},{autoAlpha: 1});

 

 

There would be no point in putting renderLoaders() right there if it doesn't return an animation. You might as well just call outside the timeline.

renderLoaders()

tl
    .to(summaryContent, {autoAlpha: 0})
    .fromTo('.loader', {autoAlpha: 0},{autoAlpha: 1});

 

This seems to be looping back to my original solution in your last topic. If you need to dynamically add some elements in a callback placed in a timeline, then you should not place those animations on the timeline, at least not initially.  

 

Link to comment
Share on other sites

Yeah I do remember reading your explanation of that in the other thread, and I think I've understood/got to that in a very round about way:

1 hour ago, NickWoodward said:

Line 2: Execute renderLoaders() immediately(?). Nothing is added to the timeline(?) and the loader is technically added to the DOM here before the animation runs


I think what's made things more clear is when you said that the entire animation runs on the next tick.

 

50 minutes ago, OSUblake said:

There would be no point in putting renderLoaders() right there if it doesn't return an animation. You might as well just call outside the timeline.

renderLoaders()

tl
    .to(summaryContent, {autoAlpha: 0})
    .fromTo('.loader', {autoAlpha: 0},{autoAlpha: 1});


ah ok - so I don't need to worry about it rendering the loaders prior to the summaryContent animating out because the .fromTo() sets the autoAlpha (virtually) instantly.

When I thought those lines of code were responsible for executing the animations (so to speak), I was worried that the autoAlpha:0 wouldn't take effect until after previous animations had run to completion. Obviously now I know that's not correct :)

Thanks again - I will try my best to only now use this thread as a reference!

Link to comment
Share on other sites

4 hours ago, NickWoodward said:

When I thought those lines of code were responsible for executing the animations (so to speak), I was worried that the autoAlpha:0 wouldn't take effect until after previous animations had run to completion. Obviously now I know that's not correct :)

 

Gotcha. from/fromTo are actually treated a little different when added to a timeline. This page has a lot of good info how they work.

 

 

Link to comment
Share on other sites

1 hour ago, OSUblake said:

from/fromTo are actually treated a little different when added to a timeline

Perfect timing, that cleared up one of the bugs I was having 😊 (*edit: in fact I think immediate rendering definitely contributed to some of my confusion)

So I've now I tried 3 different approaches. 2 work, one outside of the timeline as a callback, another adding the animation to the tl with immediateRender: false , and another doesn't at all, for pretty obvious reasons I think
 

// No point adding to tl
adminView.renderAdminLoaders();

tl
  .add(adminView.animateAdminContentOut()) // Returns a tl, added to tl
  .add(adminView.animateAdminLoadersIn(), '>') // Returns a tween, added to tl

try {
  // wait for async request here
  const data = await getData();
  
  ////////////////////////// option a
  // Callback, not an animation, so not added to tl, but executed at the right point
  tl.add(() => {
    adminView.animateAdminLoadersOut(); // returns a simple fromTo tween
    
    // nothing returned
  }); 

  //////////////////////// option b
  tl.add(adminView.animateAdminLoadersOut()); // adds a simple fromTo tween to the timeline
 
  //////////////////////// option c
  adminView.animateAdminLoadersOut(); // returns a simple tween which is executed immediately
}

Option a: executes at the right time in the tl as a simple callback. Not part of the timeline, any animation added after won't wait for it.

Option b: adds the fromTo tween to the timeline. But without immediateRender: false, the loader appears at the beginning of its parent tl (ie right at the start)

Option : executes as soon as it can, behaves weirdly because it races the other timeline

I *think* I'm starting to get this
😂


*edit: is option a the equivalent of calling adminView.animateAdminLoadersOut() in the onComplete() of adminView.animateAdminLoadersIn()?

Link to comment
Share on other sites

Got the codepen done just to check my understanding. The only thing I'm now a little unsure of is again related to the earlier problem I had (removing/re-adding a dom element and trying to select/animate it).

In order to select the right element I have to create a new tween or tl and add it inside a callback (Line 36). But this doesn't add animations to the main timeline - this means nothing after it will wait for the tween to complete (like lines 39/40)

Should I split the tl in two and add each section to a parent tl? Does that sould like it would work?


 

See the Pen bGYZWRJ?editors=0011 by nwoodward (@nwoodward) on CodePen

Link to comment
Share on other sites

Just jumping in to acknowledge I saw the note.

Don't have anything to add as I think the explanations are great (and I don't think I really follow everything that's going on)

It's a tricky subject and I don't have any lessons that I think can help add to what's been said other than some of the basic  of "call a function to return a timeline stuff" like

 

tl.add(animateSomething())

 

this topic seems more related to order of execution and what happens when functions are executed: someFunction() vs referenced: someFunction

 

I'll make a note and see if I can find an easy / practical way to break it down.

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