Jump to content
Search Community

Interrupting an animation

NickWoodward test
Moderator Tag

Recommended Posts

Hello!

So I've got some admittedly poor code for adding content to a DOM element when a menu item is clicked.
The flow goes a like this: `click menu item => remove children from wrapper => add new items to wrapper`.

I'd like to click the menu element, have the wrapper animate its opacity to zero, remove and add items (as above), and then animate back in. Pretty simple. 
The problem is my thought process is always quite basic when it comes to animations. I immediately jump to something like this:
 

gsap.fromTo('.container', 
  {opacity: 1},
  {opacity: 0}, 
  onComplete: async() => { 
    await doAsyncThings(); 
    changeContent(); 
    gsap.fromTo('.container', {opacity:0}, {opacity:1}) 
  }
);

Which even I'm sure is fairly rubbish 😄

A glaring problem that I can see is that an async function takes time, and clicking another menu item won't stop the previous animation running. I could set a flag to prevent any further menu items from being clicked while the animation is running - but that's another rubbish approach (who wants to click a menu item, then not be able to change your mind before the content has animated in?)

I no doubt should be trying to use and reverse a timeline, but the fact that I'm altering the DOM element, rather than replacing it, is confusing me somewhat. 

Would appreciate help with a more sensible approach (for the animation rather than the editing/adding of elements) if anyone has a second 😊

Thanks,

Nick

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

Link to comment
Share on other sites

Hi @mikel,

Thanks, that's a very cool way of doing it, and one that I'm definitely going to put in a pen so I can use it/learn for next time, but is there any easy way that the wrapper can remain the same and  have the content edited (as is done in the changeContent() function), rather than having 3 distinct sections?

I'm trying to animate code from an old project and I made some bad design decisions when learning 😬

No worries if not, it'll probably be good for me to refactor my terrible old code

Link to comment
Share on other sites

Hi @Cassie!

Nice solution, but it's unfortunately not quite what I'm after, although I might be able to adapt it 😊

I'm not actually trying to toggle the content by pressing the same button, I'm trying to have new content displayed when a different button is pressed. That content is fetched from a server, so I'm kind of stuck with the async call, and the problem comes when the button is pressed but the animation can't happen until the results are back from the server. In the mean time you can press a different button, which leads to both animations running.

IE my code looks ugly because of both ignorance and necessity!

Hope that makes sense!

Nick 

Link to comment
Share on other sites

Mmm.

I'm not great with promises, but I guess the issue is more that if you're calling an animation when a promise resolves, that animation's still going to get called when it resolves - regardless of whether another button's been pressed.

 

invalidate() is the GSAP piece of the puzzle - but I guess you also need a way to cancel the first promise?
https://stackoverflow.com/questions/30233302/promise-is-it-possible-to-force-cancel-a-promise

You could do a count to see if it's the last button press but that doesn't feel good to me. Lots of wasted effort for fetches that you never actually see.

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

  • Like 1
Link to comment
Share on other sites

Hi @Cassie,

Having messed around with @mikel's approach with sections, and looking at your code, this is definitely an async problem that was happening despite the animations - I think it was just hidden because I was wiping the divs in my original project so I couldn't see the effects.

I looked at the link you provided and it reminded me that Axios (which I'm using) has cancel tokens, so I guess that's how I should approach it? IE have any button press cancel the previous request, and have the animation run in the `.then()` block. Although from memory Axios is a PITA when it comes to what it considers a failed response 😄

Anyway, thanks a lot! 

  • Like 1
Link to comment
Share on other sites

I didn't have a lot of time to read through everything in detail, but the way I'd typically approach a scenario that boils down to multiple states that may have an animation going between them (and you need to be able to dynamically redirect) is sorta like: 

 

let stateTransition;

function gotoState(state) {
  if (stateTransition && stateTransition.isActive()) {
    // do whatever to get it back to the "from" state, so that could be 
    // stateTransition.reverse() or just edit the DOM here (whatever your app requires)
    // then just call this function again when it's done, maybe in an onComplete
    // or calculate the time it'll take and just use a delayedCall()...
    gsap.delayedCall(0.5, () => gotoState(state);
    return
  }
  // do state transition...
  stateTransition = gsap.to(...);
}

 

Link to comment
Share on other sites

10 hours ago, GreenSock said:

I didn't have a lot of time to read through everything in detail, but the way I'd typically approach a scenario that boils down to multiple states that may have an animation going between them (and you need to be able to dynamically redirect) is sorta like: 

 

let stateTransition;

function gotoState(state) {
  if (stateTransition && stateTransition.isActive()) {
    // do whatever to get it back to the "from" state, so that could be 
    // stateTransition.reverse() or just edit the DOM here (whatever your app requires)
    // then just call this function again when it's done, maybe in an onComplete
    // or calculate the time it'll take and just use a delayedCall()...
    gsap.delayedCall(0.5, () => gotoState(state);
    return
  }
  // do state transition...
  stateTransition = gsap.to(...);
}

 


Hi @GreenSock

Appreciate the explanation, but for the time being that definitely feels above my paygrade - I'll definitely come back to this thread at a later date when I inevitably have an issue similar to this again, and hopefully I'll be good enough to understand!
 

Link to comment
Share on other sites

I've solved the async problem by cancelling the Axios tokens in the real project.

I think that sorts me out. But I still need a simple solution to this:

Can I have a timeline where animation fades an element out, pauses, I add the content, then fades in? A bit like this:
 

  const content = await getContent();

  const tl = gsap.timeline()
    .to(element, {
          autoAlpha:0, 
          duration:0.5,
          onComplete: () => {
            clearElement();
            element.addAdjacentHTML('afterbegin', content);
            tl.reverse();
          }
    }) 

  tl.play();


^ that's in theory at least anyway. I'm just creating a more useful codepen with actual REST requests

Link to comment
Share on other sites

Sure. My brain goes to this: 

let tl = gsap.timeline();
tl.to(parent, { autoAlpha: 0 })
  .add(() => {
    clearElement();
    element.addAdjacentHTML('afterbegin', content);
  })
  .to(parent, { autoAlpha: 1 })

But if the content is gonna take some time to load and you want to wait to fade it back in until it's ready, just separate that logic a bit - start the content loading while you do a simple fade out. Then when your content is ready (doesn't matter if you're using a callback, promise, whatever), just put the fade in animation there. Don't feel like you need to mash it all into a single timeline because that model doesn't fit when you've got an arbitrary amount of lag introduced in the middle. 

 

  • Like 1
Link to comment
Share on other sites

@GreenSock Nice, thanks - just had a look at the add() function - had no idea you could add a callback like that - very cool.

@mikel @Cassie Thanks to both of you too, really helped out (especially that link Cassie - the comments pointed me to an edge case for axios instances).

Anyway, here's my ugly codepen that works kind of the way I want. Limit the network and click between the buttons and it *should* work correctly (I might have to debounce the button clicks for the quicker network responses, but slower ones work 👍).


See the Pen BamZNod?editors=1111 by nwoodward (@nwoodward) on CodePen

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