Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Declaratively Stepped Animations #96

Open
MeghV opened this issue Dec 27, 2016 · 7 comments
Open

Declaratively Stepped Animations #96

MeghV opened this issue Dec 27, 2016 · 7 comments
Labels
feature feature request to implement
Projects

Comments

@MeghV
Copy link

MeghV commented Dec 27, 2016

Hello,

I'm having trouble figuring out how to do state-based step animations using mojs in React/Redux. What I would like to accomplish is state-driven animations. For example, the first animation should play automatically, but I would like the second animation to play once the state has changed. Here's what I have so far:

The following is in my componentDidMount function:

const that = this
this.rollie = new mojs.Shape({
      shape:    'rollie'
      y:            0,
      x:            { -500 : 0 },
      duration:     1000,
      scale:        1.2,
      isShowEnd:  true,
      isShowStart:  true,
        onComplete (isForward, isYoyo) {
          console.log("rollie on page!")
          that.setState({ rollieEntered: true })
        },
    })

this.timeline = new mojs.Timeline;
this.timeline.add ( this.rollie )
this.timeline.replay()

Now, I would like to play a second animation once the component updates with state "rollieEntered" set to true. This will be called in componentDidUpdate. What I am trying to do is:

this.rollie.then({
        y:            {to : -200},
        x:            {to : -200},
        duration:     1000,
})

However, it seems that "play" has already been called, disallowing the then call to take place. What is your recommended approach on achieving stepped animations like above?

Thanks!

@legomushroom
Copy link
Member

Hi, Megh!

Thanks for the issue! You can use pause and play methods to pause the animation, create the new one and play the updated version again. This will allow the timeline to catch up with new end time since the then method will append another animation:

this.rollie
  .pause()
  .then({
        y:            {to : -200},
        x:            {to : -200},
        duration:     1000
  })
  .play();

There is demo.
Of course, then will create another animation after the current is done - it was intended to implement chaining. If you want to change the animation, use tune method instead:

this.rollie
  .pause()
  .tune({
        y:            {600 : 0},
        x:            {600: 0},
        duration:     1000
  })
  .play();

There is demo.

Hope it helps, please let me know if you have any questions.

Cheers!

@MeghV
Copy link
Author

MeghV commented Dec 28, 2016

I appreciate the help!

The .pause().tune().play() method looks like it will solve the issue. However after testing it out, it seems that the shape flashes and loses its transform styling right before playing the tuned update. However, this isn't present in your demo. Is this a side-effect of tune?

Here's my code for reference:

componentDidMount () {
    const that = this
    this.shape = new mojs.Shape({
      x:   { 500 : 0 },
      y:           0,
      duration:        5000,
      onComplete() {
        that.setState({ bounceRollie: true })
      }
    }).replay()
}

componentDidUpdate (prevProps, prevState) {
    if(this.state.bounceRollie) {
      this.shape
        .pause()
        .tune({
          x:     { 0 : 0 },
          y:     { 0 : -100 },
          duration: 1000,
        })
        .play()
    }
  }

As you can see, the state of the React component is updated, triggering the second transition.

@MeghV
Copy link
Author

MeghV commented Dec 28, 2016

It seems that using .pause().then().play() doesn't result in the flash... Any ideas?

@legomushroom
Copy link
Member

legomushroom commented Dec 28, 2016

@MeghV happy to help!

tune method doesn't know what the current coordinates of the shape are, it simply tunes the new properties to the animation. So for your animation you had the next transitions: x: (from 500 to 0) and y: (0), after that you tune them to new transitions: x: (from 0 to 0 ) and y: (from 0 to -100) thus your initial position will jump (for my demo I simply found the point where the shape will not jump).

You can also get the current properties from non-public _props object that holds current properties:

// get current `x`
const {x} = shape._props;

shape
  .pause()
  // tune animation from current `x`
  .tune({ x: { [parseInt(x, 10)]: 0 }, duration: 3000 })
  .replay();

There is demo.

Of course, you need to apply the _hide hack but I will add the next in nearest versions:

  • get public method to get the current properties instead of hijacking _props
  • method to start new animation on the current object from the current state to whatever needed - exactly what you are trying to do here. Or augment the tune to something like this:
shape
  .pause()
  // tune animation from current `x`
  .tune({ x: { current: 0 }, duration: 3000 })
  .replay();

Please let me know.

Cheers!

@legomushroom legomushroom added the feature feature request to implement label Dec 28, 2016
@MeghV
Copy link
Author

MeghV commented Dec 28, 2016

Great, this works perfectly. Thanks so much for the prompt help!

@MeghV
Copy link
Author

MeghV commented Dec 28, 2016

Also, in looking for a solution to this problem, I found another way to accomplish (state-driven) stepped animations, though it's neither as clean nor as efficient as the method mentioned above. However, it may be helpful to others as a reference to others aiming to use React to build declarative animations.

It involves separating the object's animation into two (or n) separate timelines, the second / nth timeline playing on completion of the prior one. Note: This will create two copies of the same SVG element on the page, so it's definitely not as efficient as the method mentioned above.

Here's an example - I used it to roll in a circle SVG and then bounce it repeatedly until my sites assets were loaded:

componentDidMount () {
  const that = this

  // Step 1: Roll-In Animation
  this.rollInAnimation = new mojs.Shape({
    el:       '#rollie-svg',
    shape:    'circle', 
    x:            { -500 : 0 },
    y:            0,
    duration:     3000,
    isShowEnd:    true,
    isShowStart:  true,
    onComplete (isForward, isYoyo) {
      // on completion, set state to begin 
      // the next animation
      that.setState({ startBounce: true })
    }
  })

  // Step 2: Bounce Animation
  // NOTE:
  // tween start values in this animation 
  // should equal the tween end values of the 
  // previous animation to give an impression that 
  // it's the same element
  this.bounceAnimation = new mojs.Shape({
    el:       '#rollie-svg',
    shape:    'circle', 
    x:            0, 
    y:            { 0 : -100 , curve: /* bounce curve */ },
    duration:     600,
    isShowEnd:    true,
    isShowStart:  true,
    opacity:        0, 
    repeat: 3,
    onComplete (isForward, isYoyo) {
      // you can use this to trigger 
      // the next animation after this bounce
      if(this.state.loadedState) {
        console.log("do something")
      } else {
        // if the desired state isn't ready,
        // replay the bounce 
        that.bounceTimeline.replay()
      }
    },
    onFirstUpdate () {
      // as soon as the animation is started for 
      // the first time or for repeats, the 
      // opacity should go to 1 and the previous 
      // shape should be hidden
      this.el.style['opacity'] = 1
      that.rollie._hide() 

    }
  })
  
  this.rollInTimeline = new mojs.Timeline;
  this.rollInTimeline.add ( this.rollInAnimation )
  
  this.bounceTimeline = new mojs.Timeline;
  this.bounceTimeline.add ( this.bounceAnimation )

  this.rollInTimeline.play()
}

componentDidUpdate (prevProps, prevState) {
  // once the first (or nth) animation is completed,
  // we can start the next one
  if(this.state.startBounce) {
    this.bounceTimeline.play()
  }
}

@legomushroom
Copy link
Member

Megh, welcome and thanks for the another approach!

@xavierfoucrier xavierfoucrier added this to Features in mojs@next Feb 13, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature feature request to implement
Projects
No open projects
mojs@next
Features
Development

No branches or pull requests

2 participants