wcandillon / react-native-redash

The React Native Reanimated and Gesture Handler Toolbelt
https://wcandillon.gitbook.io/redash/
MIT License
1.99k stars 117 forks source link

Delaying runTiming node flickering #89

Open eleddie opened 5 years ago

eleddie commented 5 years ago

I need to run some timing animations parallel one to the other, I managed to do this pretty easy with multiple runTimings. Now I need to add delay to some of them. My logic tells me to use runDelay with a runTiming inside of it but im getting a flickering effect.

This is one of the animations without the delay (note that there's an opacity and translate animation):

const cardSetup = animSetup(800, 1);
this.opacityCard = runTiming(cardSetup.clock, 0, cardSetup.config);
this.translateCard = bInterpolate(this.opacityCard, 0, -150);

The animSetup function is a helper function which contains this:

const animSetup = (duration, toValue) => {
  return {
    clock: new Clock(),
    config: {
      duration,
      toValue,
      easing: Easing.bezier(0.25, 0.1, 0.25, 1),
    },
  };
}

And it looks like this (works perfectly fine): runTiming

Now if I add the runDelay function:

const cardSetup = animSetup(800, 1);
this.opacityCard = runDelay(runTiming(cardSetup.clock, 0, cardSetup.config), 100);
this.translateCard = bInterpolate(this.opacityCard, 0, -150);

It looks like this: runDelayBug

I'm not sure if it is a bug on the runDelay function, a bug on react-native-reanimated or maybe a wrong way to do it.

Any idea how to accomplish this effect?

rodolfovilaca commented 5 years ago

Idk how to explain the real problem itself, if the delay which is another runTiming is delaying every set of the node but doesn't stop the animation to run, so the effect is the first position of the node and then sets the position after the delay.

In other words i think what you need is to delay the startClock of the timing animation, for this you would have to change a little bit the runTiming function to accept a delay duration like the example below:

export function runTiming(clock, value, config, delay) {
  const state = {
    finished: new Value(0),
    position: new Value(0),
    time: new Value(0),
    frameTime: new Value(0)
  };

  return block([
    onChange(config.toValue, set(state.frameTime, 0)),
    cond(clockRunning(clock), 0, [
      set(state.finished, 0),
      set(state.time, 0),
      set(state.position, value),
      set(state.frameTime, 0),
      runDelay(startClock(clock), delay)
    ]),
    timing(clock, state, config),
    cond(state.finished, stopClock(clock)),
    state.position
  ]);
}

From this you should be able to call:

const cardSetup = animSetup(800, 1);
this.opacityCard = runTiming(cardSetup.clock, 0, cardSetup.config, 100)
this.translateCard = bInterpolate(this.opacityCard, 0, -150);

Maybe we could make a proposal so that timing, spring, and decay animations should accept a delay prop to delay the startClock of each animation.

wcandillon commented 5 years ago

there is a delay function available, did you try it?

rodolfovilaca commented 5 years ago

Yes i believe he was using a version previous to 8.0.0 where the runners were renamed, and he called the runDelay runner.

You can see from this expo-snack that i built to reproduce the effect that he is talking about and my solution to delaying the clock to start.

eleddie commented 5 years ago

I'll give it a try and let you know. Thanks guys

luogao commented 4 years ago

The delay function may need a clock argument, in case combine with other function, like loop function

I have tried run an animation use delay & loop and I made it works like below:

animationValue = block([
    timing({ clock: this.clock, from: 0, to: 1, duration: this.props.delay || 0 }),
    cond(not(clockRunning(this.clock)), [ loop({ clock: this.clock, duration: 6000, easing: Easing.linear })])
])

and the source code of delay use a new Clock in itself which may cause problem

export const delay = (node: Animated.Node<number>, duration: number) => {
  const clock = new Clock();
  return block([
    timing({ clock, from: 0, to: 1, duration }),
    cond(not(clockRunning(clock)), node)
  ]);
};
luogao commented 4 years ago

The delay function may need a clock argument, in case combine with other function, like loop function

I have tried run an animation use delay & loop and I made it works like below:

animationValue = block([
    timing({ clock: this.clock, from: 0, to: 1, duration: this.props.delay || 0 }),
    cond(not(clockRunning(this.clock)), [ loop({ clock: this.clock, duration: 6000, easing: Easing.linear })])
])

and the source code of delay use a new Clock in itself which may cause problem

export const delay = (node: Animated.Node<number>, duration: number) => {
  const clock = new Clock();
  return block([
    timing({ clock, from: 0, to: 1, duration }),
    cond(not(clockRunning(clock)), node)
  ]);
};

Seems like this solution may have performance issues, 😥😥😥

Thram commented 4 years ago

@eleddie

I had the same problem and created this 2 functions that solved the problem: https://gist.github.com/Thram/e9ca9de3e4b47b9877f5bd089b5059ea You can use them like:

const animation = new Value(0);
useCode(() => set(animation, delay(loop({ duration }), delayTime)), [
    animation,
    duration,
    delayTime,
  ]);