react-native-windows icon indicating copy to clipboard operation
react-native-windows copied to clipboard

Animated.Value does not increment properly

Open Tatious opened this issue 4 years ago • 11 comments

Say for example I want a value to animate between 0 and 1. Nothing would happen if I were to do this. I must set the initial value to >0.001 for it to actually do anything.

Environment

react-native-cli: 2.0.1 react-native: 0.61.5 System: OS: Windows 10 10.0.18363 CPU: (12) x64 Intel(R) Xeon(R) W-2133 CPU @ 3.60GHz Memory: 15.79 GB / 31.73 GB Binaries: Node: 10.16.3 - C:\Program Files\nodejs\node.EXE Yarn: 1.22.0 - C:\Program Files (x86)\Yarn\bin\yarn.CMD npm: 6.9.0 - C:\Program Files\nodejs\npm.CMD npmPackages: react: 16.9.0 => 16.9.0 react-native: ^0.61.5 => 0.61.5 react-native-windows: 0.61.0-beta.22 => 0.61.0-beta.44 Installed UWP SDKs: 10.0.17763.0 10.0.18362.0

Steps to Reproduce

Use this app code: https://snack.expo.io/I5ZIbQtHJ

Expected Behavior

The opacity of the text shifts from 0 to 1 over the course of 1 second.

Actual Behavior

The Text element stays invisible forever. However, if I initialize mySmallAnimatedValue to any value > 0.001, it will work properly.

Additional context

Adam Gorman(adamgor) Austin Beaulieu(aubeauli)

Tatious avatar Mar 13 '20 18:03 Tatious

0 might be coming in as null if it's a default value

chrisglein avatar Mar 16 '20 18:03 chrisglein

@Tatious Pasting the example from https://reactnative.dev/docs/animated#example into packages\playground\Samples\animation.tsx as below, and running it works without issues. Can you try that?

/**
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 * @format
 */

import * as React from 'react';
import { useState, useRef, useEffect, useCallback } from 'react';
import {
  StyleSheet,
  View,
  Text,
  Animated,
  Easing,
  Button,
  AppRegistry,
} from 'react-native';

const stylesOne = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center"
  },
  fadingContainer: {
    paddingVertical: 8,
    paddingHorizontal: 16,
    backgroundColor: "powderblue"
  },
  fadingText: {
    fontSize: 28,
    textAlign: "center",
    margin: 10
  },
  buttonRow: {
    flexDirection: "row",
    marginVertical: 16
  }
});

export const TranslatingModule = () => {
  const fadeAnim = useRef(new Animated.Value(0)).current;

  const fadeIn = () => {
    // Will change fadeAnim value to 1 in 5 seconds
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 2000
    }).start();
  };

  const fadeOut = () => {
    // Will change fadeAnim value to 0 in 5 seconds
    Animated.timing(fadeAnim, {
      toValue: 0,
      duration: 2000
    }).start();
  };

  return (
    <View style={stylesOne.container}>
      <Animated.View
        style={[
          stylesOne.fadingContainer,
          {
            opacity: fadeAnim // Bind opacity to animated value
          }
        ]}
      >
        <Text style={stylesOne.fadingText}>Fading View!</Text>
      </Animated.View>
      <View style={stylesOne.buttonRow}>
        <Button title="Fade In" onPress={fadeIn} />
        <Button title="Fade Out" onPress={fadeOut} />
      </View>
    </View>
  );
};

AppRegistry.registerComponent('Bootstrap', () => TranslatingModule);

rectified95 avatar Mar 25 '20 18:03 rectified95

@Tatious Can you please check if the above code works properly on your machine in the Playground app?

rectified95 avatar Mar 27 '20 18:03 rectified95

Was useNativeAnimation a custom defined function? I converted the original repro into a snack by swapping that out: https://snack.expo.io/@chrisglein/repro4312

chrisglein avatar Apr 06 '20 19:04 chrisglein

I've used Chris's snippet that contains the initial repro code and pasted it in packages\playground\Samples\animation.tsx. Running it inside the Playground app I can observe the expected behavior. @Tatious this issue has received elevated priority. Can you, please, provide more details about your reproduction of the problem? For reference:

import * as React from 'react';
import {
  StyleSheet,
  View,
  Text,
  Animated,
  Easing,
  AppRegistry,
} from 'react-native';

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    paddingTop: 100,
    backgroundColor: '#ecf0f1',
    padding: 8,
  }
});

export const TranslatingModule = () => {
  let textOpacity = React.useRef(new Animated.Value(0)).current;
  let textTranslationY = React.useRef(new Animated.Value(0)).current;

  React.useEffect(() => {
    const animation = Animated.parallel([
      Animated.sequence([
        Animated.timing(textOpacity, {
          toValue: 1,
          delay: 2000,
          duration: 1000,
          easing: Easing.linear,
          useNativeDriver: true,
        }),
      ]),
      Animated.sequence([
        Animated.timing(textTranslationY, {
          toValue: 100,
          delay: 1000,
          duration: 1000,
          easing: Easing.linear,
          useNativeDriver: true,
        }),
      ]),
    ]);
    animation.start();
  });

  return (
    <View style={styles.container}>
      <Animated.View style={{ transform: [{ translateY: textTranslationY }], opacity: textOpacity }}>
        <Text>{'Hello RNW Team!'}</Text>
      </Animated.View>
    </View>
  );
};

AppRegistry.registerComponent('Bootstrap', () => TranslatingModule);

rectified95 avatar Apr 18 '20 02:04 rectified95

@namrog84 @Tatious I've created a standalone app using the CLI, and used the code above. Can confirm that the animation is rendered correctly.

EDIT: After running multiple times, I have sometimes seen the reported problem, when both opacity and yTranslation were initialized with 0 or very small values - surprisingly it didn't occur in all runs. I suggest an upgrade of RN Native in the meantime. I will also investigate this problem further, now that I have an intermittent repro.

rectified95 avatar Apr 21 '20 01:04 rectified95

The bug only repros if all 3 of these conditions are true:

  • You are animating Opacity from 0 to anything
  • You are animating both Opacity and another property
  • There is a delay on the animation

I debugged into XAML and while we're in the buggy state, the Panel being animated is stuck with Opacity set to 0, and it thinks an animation is still running.

We have special logic in XAML that culls the visuals out of the tree for nodes with Opacity = 0. I think this culling logic is somehow having a bad interaction with the animations.

I debugged the setting of Opacity to 0 and I noticed that this is getting set to a static value of 0 when the Panel is first created. It appears that React code is taking that initial animation value and turning it into the initial static property value.

I hacked the RNW code that sets static property values (In FrameworkElementViewManager::updateProperty()) to look for an Opacity of 0 and if this is true, instead set an Opacity of 0.01. With this hack, the bug no longer repros. I searched the RNW code and I don't see any place that reads Opacity back, so that is pretty strong evidence whatever is going wrong is happening down in XAML. If that's the case, we'll need to workaround this in the RNW code while we wait for a real fix in WinUI 3.0.

I have an iDNA trace with the repro, debugging through that now.

kmelmon avatar May 20 '20 05:05 kmelmon

@Tatious I'm getting close to finding a root cause. In the mean-time you could workaround this by doing any of these:

  • Animate Opacity from 0.01 instead of from 0
  • Remove the delay on the animations

kmelmon avatar May 20 '20 05:05 kmelmon

I think I've seen a similar bug recently where an opacity animation worked correctly initially, but subsequent toggling of the animated value between 0 and 1 seems to lose the visual. It happens intermittently and only when quickly toggling the animation from 1 to 0 and back to 1.

rozele avatar Jan 11 '22 01:01 rozele

Note I confirmed that the same workaround (animating between 0.01 and 1) fixes the issue, but for various reasons I don't want to do this, namely that I suspect this is not the only place composition animations for opacity are broken in our app. I suspect its the same root cause fixed in #4964 where the UIElement needs to be "dirtied", though it seems like you'd need to trace the thread from the AnimationDriver to all props nodes and re-run EnsureUIElementDirtyForRender is called each time a new animation on the value node is started.

rozele avatar Jan 11 '22 02:01 rozele

In our use case, we have a simple button that animates opacity between 0 and 1 after a certain scroll threshold is reached (it's a scroll-to-end button). If you scroll close to the boundary and quickly scroll back and forth across the threshold, occasionally the animation starts, but the opacity doesn't change. It happens without the animation being delayed, and also without any other concurrent animations running (e.g., translation).

rozele avatar Jan 11 '22 02:01 rozele