Mouscellaneous
Mouscellaneous

Reputation: 2674

ReactJS: AnimationValue.interpolate for sin and cos

I am trying to animate a component in ReactJS native so that it orbits continuously around a point in a circular fashion. Basically, I need a timer that continuously increases that I can use to set the X, Y coordinates as something like {x: r * Math.sin(timer), y: r * Math.cos(timer)}

I found this guide which has some really helpful information about interpolation but I am still not having any success:

http://browniefed.com/react-native-animation-book/INTERPOLATION.html

How can I update the position of my component like this?

Bonus question: How can I make the animation continuous?

Upvotes: 3

Views: 3152

Answers (2)

Mouscellaneous
Mouscellaneous

Reputation: 2674

I ended up figuring out a way to do this, although it is not ideal and I'd appreciate any other input. Here is my code:

class MyComponent extends Component {
    constructor(props) {
        super(props);

        this.state = {
            rotation: new Animated.Value(0),
            pos: new Animated.ValueXY()
        }
    }

    componentDidMount() {
        this.animate(10000);
    }

    animate(speed) {
        Animated.parallel([
            Animated.timing(this.state.rotation, {
                toValue: 360,
                duration: speed,
                easing: Easing.linear
            }),
            Animated.timing(this.state.pos, {
                toValue: {x: 360, y: 360},
                duration: speed,
                easing: Easing.linear,
                extrapolate: 'clamp'
            })
        ]).start(() => {
            // Cycle the animation
            this.state.rotation.setValue(0);
            this.state.pos.setValue({x: 0, y: 0});
            this.animate(speed);
        });
    }

    render() {
        let range = new Array(360);
        for (let i = 0; i < 360; i++) {
            range[i] = i;
        }

        var radius = 51,
            offset = 40;

        return (
            <View style={this.props.style}>
                <Animated.View style={{position: 'absolute', transform: [
                    // Map the inputs [0..360) to x, y values
                    {translateX: this.state.pos.x.interpolate({
                        inputRange: range,
                        outputRange: range.map(i => offset + radius * Math.cos(i * Math.PI / 180))
                    })},
                    {translateY: this.state.pos.y.interpolate({
                        inputRange: range,
                        outputRange: range.map(i => offset - radius * Math.sin(i * Math.PI / 180))
                    })},
                    {rotate: this.state.rotation.interpolate({
                        inputRange: [0, 360],
                        outputRange: ['360deg', '0deg']
                    })}
                ]}}>
                    <Image source={require('image.png')}/>
                </Animated.View>
            </View>
        )
    }
}

Basically, I use the interpolate function to map the the Animated.Value pos to the result of my function for all 360 degrees. I think this could be improved by having a sort of map function instead of an interpolate function. Does anyone know if there is one?

Upvotes: 2

Val
Val

Reputation: 22797

Since react-native interpolation doesn't support function callback, I made a helper function:

function withFunction(callback) {
    let inputRange = [], outputRange = [], steps = 50;
    /// input range 0-1
    for (let i=0; i<=steps; ++i) {
        let key = i/steps;
        inputRange.push(key);
        outputRange.push(callback(key));
    }
    return { inputRange, outputRange };
}

Apply animation to translateX and translateY:

transform: [
{
   translateX:
      this.rotate.interpolate(
         withFunction( (value) => Math.cos(Math.PI*value*2) * radius )
      )
},
{
   translateY:
      this.rotate.interpolate(
         withFunction( (value) => Math.sin(Math.PI*value*2) * radius )
      )
},
]

Visual result:

enter image description here

And to make animation continuous, I did something like this:

rotate = new Animated.Value(0);

componentDidMount() {
    setInterval( () => {
        let step = 0.05;
        let final = this.rotate._value+step;
        if (final >= 1) final = 0;
        this.rotate.setValue(final);
    }, 50);
}

Upvotes: 5

Related Questions