pmndrs / react-spring

✌️ A spring physics based React animation library
http://www.react-spring.dev/
MIT License
28.27k stars 1.19k forks source link

Multi-stage transitions #10

Closed techniq closed 6 years ago

techniq commented 6 years ago

Not sure how this would work with spring-based animations, but it would be useful to be able to have multi-stage transitions. For example, to replicate this, you:

Looking at the source, react-move (previously known as resonance) does it with delays and passing arrays to enter/update/leave. I don't know if react-spring could take in arrays as well and provide timings, or maybe explicitly specify the order of the lifecycle methods and waits for one to finish before starting the next:

<Transition
  enter={...}
  update={...}
  leave={...}
  order={'leave','update','enter'}
>
</Transition>

I don't know if this would be a good API (and work), but just another use case to think about.

drcmda commented 6 years ago

I guess it would come down to this:

  1. plain userland using onRest where the animations reside in this.state, something like this: https://codesandbox.io/s/0qxxxljrvp (in Transitions case it would now dump the animation definitions into from/enter/leave/update)
  2. Making Transition more complex
  3. Wrapping Transition into a class that reloads it using onRest

I kind of like userland because it gives lots of finecontrol and it's actually quite easy to pull off. Otherwise the option to do this out of the box would be nice, too, if it's flexible enough.

drcmda commented 6 years ago

Another idea would be a generic Keyframe primitive that would be able to define rules (arrays, onRest or delays, etc.) and simply forward stuff to the wrapping component, which could be a Spring, Transition, Trail, etc. I think i'd like that best because it's composable.

<Keyframes to={[{ opacity: 0}, { opacity: 0.5 }, { opacity: 1 }]}>
    {props => <Spring {...props}> ...</Spring>}
</Keyframes>

The only thing that would be tough is communication. Spring has to somehow inform Keyframe that it's done. Though i think either context or the new ref api can solve that.

Or ... and this wouldn't need context, by referring to the animation primitive:

<Keyframes primitive={Spring} to={[{ opacity: 0}, { opacity: 0.5 }, { opacity: 1 }]}>
    {styles => <div style={styles}>...</div>}
</Keyframes>
techniq commented 6 years ago

How would you determine the lifecycle you are in to know how to animate (how does the component know it's entering vs updating vs leaving...).

Your case here would be helpful when you want to have some explicit steps alone the way. For example, start at the top left, animate down 100, then to the right 100:

<Keyframes primitive={Spring} to={[{ x: 0, y:0}, { x:0, y:100 }, { x:100, y:100 }]}>
    {styles => <div style={styles}>...</div>}
</Keyframes>

this might be possible if you allowed passing an array in the to/from of Spring or enter/update/leave of Transition but I could see some benefit in an explicit primitive.

You could maybe pass a delay prop to Keyframes this way (instead of having some reserved props in the styles you passed like react-move does.

Maybe you could flip it around and return Keyframes in to/from, etc

<Spring
  to={<Keyframes values={[...]} />}
/>
drcmda commented 6 years ago

This isn't exactly tied yet to the Transition component, but a little work on this and maybe we have a generic component that can script just about anything. I am seeing it do chains, react to user interaction or switch primitives (go from trail to transition for instance). I am not sure however if this will easily solve your usecase, although the script could probably add stuff, wait, delete stuff, wait, update again, etc.

https://codesandbox.io/s/j1w4355593

keyframes

techniq commented 6 years ago

@drcmda that's looking great!

techniq commented 6 years ago

I like the imperative nature of <Keyframe script={async () => {...} />. I just now looked at React-Native's Animated and saw they have parallel, sequence, stragger, and delay. To sure if a similar preset could be offered here as well?

<Sequence>
  <Spring ... />
  <Spring ... />
  <Spring ... />
</Sequence>

I like in your example you don't have to await one of your frictionless (but add an explicit delay). Could also maybe have:

<Sequence>
  <Spring ... />
  <Spring await={false} ... />
  <Delay value={2000} />
  <Spring ... />
</Sequence>

just bike-shedding right now 😄

drcmda commented 6 years ago

More primitives would be great anyways. That's the neat thing about animated, wrapping it isn't expensive. Sequential chains or parallel chains, makes sense to do it declaratively.

PS. made a slightly more complex demo: https://codesandbox.io/embed/zl35mrkqmm

joshperrin commented 6 years ago

Hey there, I've been scouring the docs and examples trying to figure out how to do a Keyframes.Transition, but have yet to find a solution. I see in the Full API docs that the Keyframes has Transition functionality, but I can't seem to figure it out.

Maybe I'm not understanding the functionality completely, I was expecting to be able to use the keyframe to create a multi staged transition on enter and leave. I'm looking to have an element animate a transform, and then animate width when an item enters, and then reverse that when it exits.

Wondering if you could point me in the right direction, thanks!

drcmda commented 6 years ago

You use Keyframes when you need to declare slots like open/closed/... or if you want to drive an animation by code. Your case seems to be a simple transition with from/enter/leave/update props.

from = base state

enter = from -> appearing state

leave = current -> disappearing state

update = an update happened that was neither an exit or leave (other elements went in or out)

You can look into mauerwerk's source for some ideas: https://github.com/drcmda/mauerwerk/blob/master/src/index.js

If by multi-stage you mean waiting out until X enters, then exit Y, etc. then this would only be possible if you write a wrapper that queues items and listens for onRest. I wouldn't call this keyframing or chaining, it's just a highly specific transition type that ´Transition´ out of the box doesn't go into.

If you have suggestions or want to try a PR if you need additional functionality, you can open a new issue.

joshperrin commented 6 years ago

Is it possible in the enter transition to have it have two stages? Kind of like the keyframe can accept a function, and then delay or wait between animations?

I'm looking for my enter animation on my Transition to animate the width property, wait for that to finish, then animate the transform property.

thanks!

drcmda commented 6 years ago

@joshperrin i think it could be possible with the update prop. I'm super busy right now to delve into this but in theory it should work. Probably the easiest would be to just give those two props different configs:

enter={{ width: 100, transform: 'scale(1.5)' }} config: { name => 
  name === 'width'
    ? ({ tension: 10, friction: 10 })
    : ({ tension: 10, friction: 50 })

When you use the ocillator spring (look for it on the GH front page), then you can also play around with the "mass" property.

drcmda commented 6 years ago

@techniq @joshperrin took a while, but transition has a delay prop now that can trail adds/removes. See: http://react-spring.surge.sh/transition#props

It doesn't yet allow ordering and updates aren't trailed, but the foundation is there and it could easily be adjusted.