Jump to content
Search Community

Using timeline.set after removing old HTML and prepending new HTML

Chromium test
Moderator Tag

Go to solution Solved by GreenSock,

Recommended Posts

The CodePen is hopefully self explanatory. I'm going to lose my mind over why the second t1.set is not working. Also, assuming we do get it working, ideally, I'm hoping that there won't be a flash of content before the 'margin-right' is applied by the t1.set.

 

Side note: any idea why it's choppy for the first 75% of the animation?

See the Pen oNENWYb by fuad-zeyad-tareq (@fuad-zeyad-tareq) on CodePen

Link to comment
Share on other sites

It's a logic issue in your code - you're creating your timeline initially and the final .set() affects all the elements in _listEls BUT (and here's the problem) when your .call() function runs, it removes all the _listEls (the ones that the .set() is affecting) and prepends entirely new ones. So GSAP is setting the properties exactly as you told it to on those elements...but those elements are gone from the DOM at that point. You're setting the wrong elements. 

 

I'm not exactly sure what you're trying to do but you could probably just do a simple gsap.set() at the end of that .call() function: 

See the Pen wvyveNv?editors=1010 by GreenSock (@GreenSock) on CodePen

Link to comment
Share on other sites

Hi Jack,

 

Thanks for the explanation. I figured it's a logic issue because I'm a little unclear about how the timeline works here. Looking at your code it seems all you have done is make the .set block into its own tween and move it inside of the t1.call. However, I am hoping there's a way to do the .set on the t1 timeline so I can continue animating these elements (and possibly other elements within the same timeline as well) ?

I guess where my confusion lies is that I thought the t1.call happens in logical order in the timeline, so when I did t1.set, I expected that to happen after the t1.call is done with whatever it's doing (meaning remove and prepend the new elements and update the _listEls with the new selector accordingly). But it seems that is not the case... does the t1.call return before whatever is inside it is executed? If that is so, then is there a way to make the timeline wait for the t1.call to finish first (or is there a better way to go about this that I'm missing) ?

Link to comment
Share on other sites

Yes, you misunderstood. When you create a timeline, the creation code executes at the same time but the actual animation and playing out of the animation happens over time. Sorta like this: 
 

let a = 0;

setTimeout(() => a = 1, 500);

console.log(a); // 0! Not 1.

Even though the console.log() is positioned AFTER that setTimeout() call in terms of reading the code, it executes on the same tick as where a is set to 0. The function that sets a to 1 doesn’t happen for 500ms.

 

In your case, you create that timeline when the variable is referencing a certain group of elements so those elements are recorded with that set/tween…but then later your function gets called that CHANGES that variable to reference a totally different group of elements. It’s not like that variable then goes back and swaps things into all the other places in the code that previously used it.

 

let a = 0;
let b = a;
a = 100;
console.log(b); // 0, not 100!

So what you seem to be asking is: “can I build a timeline initially that uses targets that don’t even exist until much later?” That’s a logical impossibility. You can, however, progressively build a timeline if you’d like:

let tl = gsap.timeline();
tl.to(...);
tl.call(() => {
  tl.to(...);
  tl.call(() => {
    tl.to(...);
    ...
  });
});

There’s even a helper function in the docs that’ll make it cleaner. See https://greensock.com/docs/v3/HelperFunctions#progressiveBuild

 

But typically you don’t need to do that - you can just use an onComplete on a timeline that then creates another animation. Why do you need to have it in exactly the same timeline instance? Are you needing to rewind the whole sequence or something? 

  • Like 1
Link to comment
Share on other sites

Quote

So what you seem to be asking is: “can I build a timeline initially that uses targets that don’t even exist until much later?” That’s a logical impossibility.

Haha yes, while that might be my end goal, the getting there is not limited to my own explanation of it. I'd like to get there in the simplest way that gets me there even if that's a different way as long as it will still allow me to efficiently achieve my end goal.

 

Quote

You can, however, progressively build a timeline if you’d like

Thank you for this suggestion. I think this might be a bit overkill for what I'm trying to achieve. Hopefully, there's a simpler way. Let me explain my end goal further below.

 

Quote

But typically you don’t need to do that - you can just use an onComplete on a timeline that then creates another animation. Why do you need to have it in exactly the same timeline instance? Are you needing to rewind the whole sequence or something?

Let me explain what exactly I'm looking to achieve (from start to end) and perhaps you can point me to the best way to go about it:

  1. Animate _listEls (as is already done at the start of the timeline in my CodePen example).
  2. Remove _listEls and prepend new _listEls (as is already done in the t1.call in my CodePen example).
  3. Re-animate the entrance of the new _listEls from the step above (this is where the .set followed by probably a .to would come in).
  4. During all of the above, I'd like to also animate a whole separate element in the DOM (let's call it _el2). So ideally, _el2's animation (or series of animations) total duration will span the duration of the animations of the elements from steps 1-3.

I thought that the simplest, cleanest, and most concise way to go about all of these animations is to keep them all under the same timeline. But as you've explained, it seems that due to step 2, that is impossible. So what's the cleanest way you'd suggest to go about this?

 

Edit: Maybe I'm overthinking this (as I usually do). Maybe I'll just finish the _listEls animations inside of the t1.call like you suggested (speaking of... doing t1.set inside the t1.call after removing and prepending the HTML seems to work in my dev environment but not in the CodePen example for some mysterious reason). And have a whole separate t2 timeline for the other element (_el2) that will run the other animations simultaneously to t1. I'm assuming this is the simplest and most elegant way of going about this.

Link to comment
Share on other sites

  • Solution
12 hours ago, Chromium said:

So what's the cleanest way you'd suggest to go about this?

Are you literally just trying to make that first animation play twice? If so, the simplest way would be to set repeat: 1 on that timeline. Done. No need to remove elements, re-add them, re-create another whole animation, etc. 

 

But assuming you actually need to animate different ones the second time around, the easiest way to do it would be to just reuse a function sorta like: 

See the Pen MWQWZQr?editors=0010 by GreenSock (@GreenSock) on CodePen

 

As for your other concurrent animation, you can run that separately - there's no need to have it all in one timeline unless you need to control everything as a whole.

  • Like 2
Link to comment
Share on other sites

Hi again Jack,

 

I'm back with this same example. I'm having another problem now after having applied what we last discussed as can be seen in this CodePen:

See the Pen mdXyRaQ by fuad-zeyad-tareq (@fuad-zeyad-tareq) on CodePen

 

It seems that spamming the button unleashes all sorts of problems. Given that I do not want to prevent the user from being able to spam the button, I am unsure as to how to solve these problems. I could have swore I solved a problem just like this before but it seems I can't learn from my own mistakes 😅

 

I thought that if I initialized the t1 variable outside of the onclick event handler, that would solve it but that didn't work. Currently, it seems that spamming the button not only breaks the animations but also leaves duplicate lis all over the place (I'm guessing is due to the appended elements failing to be removed as well).  Is there a way to allow the animation to be spammed but prevent it from breaking (and by proxy, it hopefully prevents the duplicate leftover lis) ?

Link to comment
Share on other sites

You could probably use that external "t1" variable and then at the top of your click handler, force it back to the start and kill it (or jump to the end) to ensure that it's done before moving on. Like

if (t1) {
  t1.progress(0).kill();
}

 

It really depends on exactly what you want to happen when the user clicks the button again before the animation finishes. There are many ways you could handle it. 

 

We're happy to answer GSAP-specific questions, but walking through logic issues or doing "build-to-order" solutions is a bit beyond the scope of help we can offer in the free forums :)

  • Like 1
Link to comment
Share on other sites

Unfortunately, while your tip worked for my CodePen:

See the Pen mdXyRaQ by fuad-zeyad-tareq (@fuad-zeyad-tareq) on CodePen

 

It didn't work for my current project which is similar but with like 10X the complexity. Kept having duplicating items and/or half animations even though I tried both .progress(0) and .progress(1) with and without .kill(). There's probably a logic bug  somewhere... and there's plenty of crevices for that one to be hiding in lol

 

So I opted for the next best thing for my project which is to prevent any animation restarts when there's already one happening by attaching a data API variable to the DOM element. A trick that I've also learned recently thanks to Blake the legend!

 

It's a shame as I would have liked my button to respond to every click. But I've got a metric ton of other things I've to sort out for now... maybe I'll figure this one out some other day but that day won't be today! Can't win 'em all ya know

 

You guys always go above and beyond so thanks for all your help getting me this far!

 

PS: Always thought this was a paid access forum. The fact that this is free and you help everyone as much as you do (and as fast as you do!) is mind blowing! Keep rockin'

 

Edit: Looking at my last CodePen again, it seems your trick worked when spamming the button during the first timeline of my animation but not when it's during the second timeline (Or it did, but it's not how one would want it to behave) hahaha. I suspect a separate handling needs to be added to detect which timeline is the animation currently part of (at the start of the onclick handler) and decide to progress the appropriate timeline based on that. Man my current project has so many little details just like this one that need to be individually addressed. It seems that if left towards the end, they become this big mind-boggling monstrosity you have to deal with. I think that's enough for tonight lol

  • Like 1
Link to comment
Share on other sites

20 hours ago, Chromium said:

Looking at my last CodePen again, it seems your trick worked when spamming the button during the first timeline of my animation but not when it's during the second timeline (Or it did, but it's not how one would want it to behave) hahaha.

Yep, that'd be easy enough to handle by just setting the t1 variable to be that one. Sorta like:

t1.to(...).call(() => {
  t1 = gsap.timeline(...).to(...);
});

The point is to just make sure that variable references whatever the current animation is so that when the button is clicked again, it rewinds/kills that one. 

 

It's all doable, but like you said it's logic/engineering stuff related to how you're setting things up. I totally get that you've got a lot of complexity that can spin out of control if you don't plan it all out well ahead of time and sometimes you've gotta just run with what you've got by adding some conditional logic and not letting the button respond to clicks in some cases (though that is a pet peeve of mine...and it sounds like yours too). 

 

20 hours ago, Chromium said:

PS: Always thought this was a paid access forum. The fact that this is free and you help everyone as much as you do (and as fast as you do!) is mind blowing! Keep rockin'

 

Very kind of you to say. Thank you! Yeah, we burn so many hours every day in these forums, mostly for people who never pay us a dime :) But we trust that if we just keep doing a good job serving people and putting out quality products, the enough of the market will reward our efforts and we'll be fine. Thanks for being a Club GreenSock member! 🙌

  • Like 1
Link to comment
Share on other sites

Quote

The point is to just make sure that variable references whatever the current animation is so that when the button is clicked again, it rewinds/kills that one.

Awesome! That's another good tip to know. Thank you.

 

Quote

and sometimes you've gotta just run with what you've got by adding some conditional logic and not letting the button respond to clicks in some cases (though that is a pet peeve of mine...and it sounds like yours too).

Hahaha absolutely. To be accurate, bad quality code is a pet peeve of mine. But we must balance out how our time is spent, otherwise this happens:

while(1) {
  // Chromium still re-writing his own code to improve it.
}

I'd be stuck in an infinite loop re-writing my own code to improve it 😂

 

Quote

Thanks for being a Club GreenSock member! 🙌

No need to mention. Your product is a quality product, not sure I've seen a better compatibility from any other JS library out there. I'd even argue that it should be baked into JavaScript at this point... but you know what? Scratch that. Your library is too good for JavaScript. JavaScript is easily my least favorite language out there (not sure if you could tell lol) but your library makes even JavaScript more tolerable so that's saying something...

 

Side note: I just wasted an hour trying to solve a problem that turned out was only a problem because of a... well take a look at this:

if ( _square.hasClass('square1') )
     _square.removeClass('square1').addClass('remove');
if ( _square.hasClass('square2') )
     _square.removeClass('square2').addClass('square1');
if ( _square.hasClass('square3') )
     _square.removeClass('square3').addClass('square2');
if ( _square.hasClass('square4') )
     _square.removeClass('square4').addClass('sqaure3');
if ( _square.hasClass('square5') )
     _square.removeClass('square5').addClass('sqaure4');

For better context: I was simply removing "square1" and appending a new "square5" on click of a button.

Still haven't caught the issue? I'll give you a hint, look at "sqaure3" and "sqaure4" 🤣

The stupid time I wasted looking into so many other things (jQuery.remove(), jQuery.append(), order of operations, etc...) not realizing it was just a spelling mistake. I even posted the problem on Stackoverflow and got 2 answers for it (that completely re-worked my simple snippet, confused me even more, and didn't explain why my simple code didn't work lol). It is days like these that make me think I should retire from this profession.

  • 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.
×
×
  • Create New...