Hi,
I was wondering what currently would be the best way of achieving a sequence of animations on a single element ? So essentially, a way of looping through an array of destination values (spring configs) ?
Obviously it would be lovely if you could pass in an array of spring configs to the Motion component which would detect once one spring has finished and the next should start until it gets to the final spring config, although this just wishful thinking X) !
Great library, really like the way of thinking of animations in terms of a spring, really like where you're coming from and going with the library (react talk in summer was one of my favourites)
That is pretty cool thing to have. I think this should be made by a fairly simple wrapper-component.
@nkbt hmmm good point, will have a go at making a simple wrapper component (still quite new to react) to achieve this and post my results :+1:
Although there's no way to detect whether an animation has completed unless you just check the values being passed in right?
:ok_hand:
It seems simple enough:
import React, { Component } from 'react';
import {Motion} from 'react-motion';
class SequentialMotion extends Component {
getInitialState() {
return {
i:0
};
}
componentDidMount() {
//add some kind of listener to callback animationEnded
}
animationEnded() {
if(this.state.i < this.props.styles.length){
this.setState({
i: this.state.i++
});
}
}
render() {
return (
<Motion style={this.props.styles[this.state.i]}>
{this.props.children}
</Motion>
);
}
}
export default SequentialMotion;
The missing part is how to detect when one animation ends and the other should begin ?
UseCase for #139 ?
Any ideas how it could be possible for the time being ?
There is a mutation obeserver but it's only compatible with newer browsers and still isn't easy to tell if something has finished changing, only that its changing...
Perhaps animationend is the answer ?
No event listener that I can find is suitable :(
Usually we detect that animation is ended case by case comparing current values with end value. There is no generic solution at the moment to tell that whatever animation is there - it is ended. So yes. I guess to make a generic wrapper you need one, #139 is a blocker.
This might be a noob question but there's no way of accessing the value that's being interpolated within the <Motion> component from a wrapper component in the master branch because it's not being exposed is there? To then potentially be able to to try and calculate the velocity ?
Well... I guess, you can just get styles prop, and see what is being changed, so you have end values, actually. For each step. So you can track it on step-by-step basis. My previous answer is not completely correct then. #139 would simplify the implementation a lot, but it is possible to make a wrapper without it.
but there's still an issue with the spring, as if you want to add a "wobbly" animation to the sequence, it's going to go past the end value and so you'd need to calculate the "velocity" to wait for it to go to 0....
So from my understanding of your answer, styles prop is directly mutated in the <Motion> component ?
There is no mutation, it goes through values and calls Child function When you think animation is done (to some degree of precision), just start next step. And so on. Spring calculation does not hold start/end values, it works with intermediate values.There is no state in RM or sort of queue. Animation will just "follow" new values. Technically you will make animation 100->0.01->99.98->0.01->100 or something like that for wobble.
Sorry I know I'm clearly missing something simple and don't understand but imagine I've got this render function which is using the <SequentialMotion> wrapper:
render() {
var animationStyles = [{
left:0,
top:0
},{
left:spring(100, presets.wobbly),
top:spring(100, presets.wobbly)
},{
left:spring(0, presets.wobbly),
top:spring(0, presets.wobbly)
}];
return (
<SequentialMotion styles={animationStyles}>
{({left, top}) =>
<span style={{
transform: `translate(${left}px, ${top}px)`,
WebkitTransform: `translate(${left}px, ${top}px)`,
display: 'inline-block'
}}>
{this.props.letter}
</span>
}
</SequentialMotion>
);
}
How do I track the values in this case ({left, top}) in the callback(for now non generically so I can get an understanding of how I'd watch the values of them) in <SequentialMotion>. I don't understand how I can access them from <SequentialMotion>.
Thanks for your patience with me by the way
{({left, top}) =>
these. you have them on each step. compare with:
},{
left:spring(100, presets.wobbly),
top:spring(100, presets.wobbly)
internally in SequentialMotion:
render() {
const {children: render} = this.props();
return <Motion>{style => {
doSomethingWithStyleHere();
return render(style);
}
}
Don't know how internally it is implemented in SequentialMotion, so can't be more specific.
Hope this helps!
Had another thought, would it not be possible to use
Going to try it tonight to see if I'm understanding StaggeredMotion correctly...
I recently posted a similar issue. The code sample I have shows a way to flatten a group of elements into a single object for processing by RM.
The code seems like a hack-around to me, as I would rather RM support nested property sets. It seems like support for nested property sets would resolve this issue as well.
Either way, my code sample may provide an approach to resolve this issue as it did mine.
https://github.com/chenglou/react-motion/issues/226
import React, { Component } from 'react';
import { Motion } from 'react-motion';
import _ from 'underscore';
export default class MotionSequence extends Component {
constructor(props: Object) {
super(props);
let sequenceId = 0;
console.log(this.props.styles[0]);
let defaultStyle = this.props.defaultStyle;
if (!defaultStyle) {
defaultStyle = {};
_.each(this.props.styles[0], (style: any, prop: string) => {
defaultStyle[prop] = _.isObject(style) ? style.val : style;
});
sequenceId = 1;
}
this.state = {
sequenceId,
defaultStyle
};
}
animationEnded() {
setTimeout(() => {
if (this.state.sequenceId < this.props.styles.length - 1) {
this.setState({
sequenceId: this.state.sequenceId + 1
});
}
}, 0);
}
render() {
const currentStyle = this.props.styles[this.state.sequenceId];
return (
<Motion
defaultStyle={this.state.defaultStyle}
style={currentStyle}
onRest={::this.animationEnded}
>
{this.props.children}
</Motion>
);
}
}
<MotionSequence styles={[
{ height: 0 },
{ height: spring(30) },
{ height: spring(100) },
{ height: spring(50) }
]}>
{interpolatingStyle => <div style={{...interpolatingStyle, border: '1px solid' }} />}
</MotionSequence>
This one helped me. Might be useful for someone.
@stnwk 's solution looks good, but I am receiving an error: Unexpected token at onRest={::this.animationEnded} on the first colon. Why is this?
@Rcuz8 You probably don't have the required babel-transform-plugin installed: https://babeljs.io/docs/en/babel-plugin-transform-function-bind
You can try this instead:
...
onRest={this.animationEnded.bind(this)}
...
Either way: this issue is pretty old (2016) and I have no idea whether this is still a good solution, so keep that in mind! Maybe you wanna take a look at some other animation lib for react react-spring.
Most helpful comment
This one helped me. Might be useful for someone.