2

I'm totally new to animations in react-native and I'm trying to create an animated pulsating button using the react-native-reanimated library.

Animation concepts are really not that clear for me yet but by modifying someone else code, I got pretty close to what I want to create.

I would like to make this pulsating animation continuous. Currently, it pulsates and then stops. I'd appreciate some help with this. I'm including both the code and the snack for you to see a running sample. Please keep in mind that I simply modified someone else's code so I'm sure there are things in this code that are NOT necessary. I'm learning as I work on this button.

Here's a link to the snack: https://snack.expo.io/@imsam67/reanimated-test

And here's the code:

import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';

import Animated from 'react-native-reanimated';

const {
  divide,
  set,
  cond,
  startClock,
  stopClock,
  clockRunning,
  block,
  spring,
  debug,
  Value,
  Clock,
} = Animated;

function runSpring(clock, value, dest) {
  const state = {
    finished: new Value(0),
    velocity: new Value(0),
    position: new Value(0),
    time: new Value(0),
  };

  const config = {
    toValue: new Value(0),
    damping: 10,
    mass: 5,
    stiffness: 101.6,
    overshootClamping: false,
    restSpeedThreshold: 0.001,
    restDisplacementThreshold: 0.001,
  };

  return block([
    cond(clockRunning(clock), 0, [
      set(state.finished, 0),
      set(state.time, 0),
      set(state.position, value),
      set(state.velocity, -2500),
      set(config.toValue, dest),
      startClock(clock),
    ]),
    spring(clock, state, config),
    cond(state.finished, debug('stop clock', stopClock(clock))),
    state.position,
  ]);
}

export default class Example extends Component {
  constructor(props) {
    super(props);
    const clock = new Clock();
    this._trans = runSpring(clock, 10, 150);
  }

  componentDidMount() {}

  render() {
    return (
      <View style={styles.container}>
        <Animated.View
          style={[styles.circle, { borderWidth: divide(this._trans, 5) }]}>
        </Animated.View>
      </View>
    );
  }
}

const BOX_SIZE = 100;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'black',
  },
  circle: {
    backgroundColor: "white",
    borderColor: "red",
    borderRadius: 150,
    height: 150,
    width: 150
  }
});
Sam
  • 26,817
  • 58
  • 206
  • 383

2 Answers2

2

A quick way to get this animation to loop is to set the damping to 0. This will keep the spring animation going indefinitely.

const config = {
  toValue: new Value(0),
  damping: 0, // changed to 0
  mass: 5
  stiffness: 101.6,
  overshootClamping: false,
  restSpeedThreshold: 0.001,
  restDisplacementThreshold: 0.001,
};

But you would want to change the borderWidth style to divide by a larger number to keep the border radius from overshooting.

<Animated.View
  style={[styles.circle, { borderWidth: divide(this._trans, 25) }]}>
</Animated.View>

You can find the modified Snack here.

For a repeating animtion like this, you could also look at using Lottie, which is much simpler to implement, but less flexible.

Also, you could look at using loop from react native Animations which should allow you to set the border radius.

nipuna-g
  • 6,252
  • 3
  • 31
  • 48
  • Thank you for your help. Noticed something though: if you select web, it works exactly the way I want it but running it on `iOS` or `Android` doesn't produce the looping animation. Any idea why? – Sam Feb 12 '21 at 16:19
  • Also, the reason why I wanted to handle this animation in `react-native-reanimated` is because it runs in the native `UI` thread instead of the `JS` thread. Especially in this particular animation's case, running it in the `JS` thread would produce bad results i.e. dropped frames. I'm not sure but I think `Lottie` and `Loop` also run in the `JS` thread, don't they? – Sam Feb 12 '21 at 16:22
  • I've just tested the snack on Android and iOS and it seems to be looping for me? Not entirely sure why it would not be working. You could try making a small edit to trigger a re-run which might fix the issue. – nipuna-g Feb 12 '21 at 17:39
  • 1
    As for the performance, in Lottie, you will be providing a JSON file which will then be passed on to the native code. This should give decent performance as it's not running on the native thread. And for Animation, you can set useNativeDriver to run changes in the native UI, but you can only modify a few style properties(I'm not sure if border radius is included, but you could emulate the same effect by transforming a circle shape). – nipuna-g Feb 12 '21 at 17:45
  • So, Lottie does run in the native thread! That's good to know. Thank you for letting me know! I guess for a typical animation like animating a scroll or a button, the performance difference may not be as noticeable but a button like this one that will keep pulsating, using the JS thread would be a terrible idea. – Sam Feb 13 '21 at 19:37
0

Another way to do it a bit more controlled with reanimated 1.x.x is:

const [clock] = useState(() => new Clock());
const loopingValue = useMemo(() => {
const state = {
  finished: new Value(0),
  position: new Value(0),
  time: new Value(0),
  frameTime: new Value(0),
};

const config = {
  duration: new Value(2000),
  toValue: new Value(1),
  easing: Easing.linear,
};
const value = block([
  // start right away
  startClock(clock),

  // process your state
  timing(clock, state, config),

  // when over (processed by timing at the end)
  cond(state.finished, [
    // we stop
    stopClock(clock),

    // set flag ready to be restarted
    set(state.finished, 0),
    // same value as the initial defined in the state creation
    set(state.position, 0),

    // very important to reset this ones !!! as mentioned in the doc about timing is saying
    set(state.time, 0),
    set(state.frameTime, 0),

    // and we restart
    startClock(clock),
  ]),

  state.position,
]);
return interpolate(value, {
  inputRange: [0, 0.5, 1],
  outputRange: [0, 1, 0],
});
}, [clock]);

A lot of this code is copied from a github thread here: https://github.com/software-mansion/react-native-reanimated/issues/162