Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v2 Animate Colors #845

Closed
browniefed opened this issue May 31, 2020 · 27 comments · Fixed by #1591
Closed

v2 Animate Colors #845

browniefed opened this issue May 31, 2020 · 27 comments · Fixed by #1591

Comments

@browniefed
Copy link
Contributor

browniefed commented May 31, 2020

Description

Animating colors with withTiming or withSpring doesn't seem to work with colors values (hex or RGB).

Screenshots

Kapture 2020-05-30 at 17 28 53

Steps To Reproduce

Expected behavior

Animate colors

Actual behavior

Color just changes

Snack or minimal code example

import Animated, {
  useSharedValue,
  withSpring,
  useAnimatedStyle,
  Easing,
  withTiming,
} from 'react-native-reanimated';
import {View, Button} from 'react-native';
import React from 'react';

function getColor() {
  return (
    '#' +
    ('000000' + Math.floor(Math.random() * 16777215).toString(16)).slice(-6)
  );
}
function hexToRGB(h) {
  let r = 0,
    g = 0,
    b = 0;

  // 3 digits
  if (h.length == 4) {
    r = '0x' + h[1] + h[1];
    g = '0x' + h[2] + h[2];
    b = '0x' + h[3] + h[3];

    // 6 digits
  } else if (h.length == 7) {
    r = '0x' + h[1] + h[2];
    g = '0x' + h[3] + h[4];
    b = '0x' + h[5] + h[6];
  }

  return 'rgb(' + +r + ',' + +g + ',' + +b + ')';
}

export default function AnimatedStyleUpdateExample(props) {
  const randomWidth = useSharedValue(10);
  const color = useSharedValue(hexToRGB(getColor()));
  const style = useAnimatedStyle(() => {
    return {
      width: withSpring(randomWidth.value),
      backgroundColor: withTiming(color.value, {
        duration: 500,
        easing: Easing.bezier(0.5, 0.01, 0, 1),
      }),
    };
  });

  return (
    <View
      style={{
        flex: 1,
        flexDirection: 'column',
      }}>
      <Animated.View style={[{width: 100, height: 80, margin: 30}, style]} />
      <Button
        title="toggle"
        onPress={() => {
          color.value = hexToRGB(getColor());
          randomWidth.value = Math.random() * 350;
        }}
      />
    </View>
  );
}

Package versions

  • React:
  • React Native: v2 Playground repo
  • React Native Reanimated: v2
@jakub-gonet
Copy link
Member

@browniefed, why did you close this?

@browniefed
Copy link
Contributor Author

@jakub-gonet ah I was doing it wrong, should be interpolating. Although I still don't think top level coloring is handled after reviewing some stuff

@jakub-gonet
Copy link
Member

Cool, thanks for the response, if you find another issue regarding colors feel free to reopen.

@nandorojo
Copy link
Contributor

Has anyone managed to get colors to work? If I put a color in withSpring, I see this error:

Screen Shot 2020-11-14 at 7 42 03 PM

@jakub-gonet
Copy link
Member

@nandorojo, please show us code that is crashing, we can't help you based only on the stack trace.

@nandorojo
Copy link
Contributor

Yup sorry about that, I'll add a code sample here.

@nandorojo
Copy link
Contributor

nandorojo commented Nov 18, 2020

This code produces that error:

import React, { useReducer } from 'react'
import Animated, { useAnimatedStyle, withSpring } from 'react-native-reanimated'
import { Button } from 'react-native'

export default function ColorError() {
  const [backgroundColor, toggle] = useReducer(
    (bg) => (bg === 'red' ? 'green' : 'red'),
    'red'
  )
  const style = useAnimatedStyle(() => ({
    backgroundColor: withSpring(backgroundColor),
  }))
  return (
    <Animated.View
      style={[
        {
          flex: 1,
          alignItems: 'center',
          justifyContent: 'center',
        },
        style,
      ]}
    >
      <Button title="Toggle Color" onPress={toggle} color="white" />
    </Animated.View>
  )
}

I'm using the expo reanimated starter. Here's my package.json:

{
  "version": "39.0.0",
  "dependencies": {
    "expo": "~39.0.0",
    "expo-status-bar": "~1.0.2",
    "react": "~16.13.0",
    "react-dom": "~16.13.0",
    "react-native": "https://github.com/expo/react-native/archive/sdk-39.0.2.tar.gz",
    "react-native-gesture-handler": "^1.8.0",
    "react-native-reanimated": "2.0.0-alpha.6.1",
    "react-native-web": "0.13.13",
    "react-navigation-stack": "^2.8.4"
  },
  "devDependencies": {
    "@babel/core": "^7.8.6",
    "babel-preset-expo": "~8.1.0",
    "eslint-config-nando": "^1.0.10"
  },
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo web",
    "eject": "expo eject"
  },
  "private": true
}

I also get this TS error:

Screen Shot 2020-11-18 at 10 17 23 AM

@jakub-gonet
Copy link
Member

withSpring takes a number as an argument, you can't use a string there. What do you want to achieve here? If you want to interpolate between two colors you can use interpolateColors (which is undocumented, but it should work).

@nandorojo
Copy link
Contributor

nandorojo commented Nov 19, 2020

Got it, that makes sense. I'm basically trying to animate from one color to the next, where I keep the color as a React state value. Whenever the color changes, I animate to the next one.

I'm working on a component library similar to framer-motion, powered by reanimated 2.

Link: https://github.com/nandorojo/redrip
Background: nandorojo/dripsy#46

I’m hoping to pass backgroundColor to a custom animate prop. Whenever it changes, it automatically animates, similar to transition-property: background-color in CSS.

If you're interested, you can see how I'm doing it in src/redripify/use-map-animate-to-style.ts

I assume interpolateColors only works for interpolating based on a given animate node. In my case, however, I'm not creating any animated nodes -- instead, I'm using useAnimatedStyle directly, as shown in my example above.

Maybe I could create an animated value under the hood for every possible color style value, but it doesn't seem ideal.

Do you have any suggestions @jakub-gonet? Thanks so much for your time!

@jakub-gonet
Copy link
Member

I don't maintain Rea2 and have limited knowledge about it but @zrebcu411 or @piaskowyk maybe know the answer for this one?

@nandorojo
Copy link
Contributor

I tried using processColor for the color values. However, this leads to an odd flickering.

import { View, Button, StyleSheet } from 'react-native'
import React from 'react'
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withTiming,
  processColor, // I tried this
} from 'react-native-reanimated'

const colors = ['rgb(5,200,3)', 'rgb(10,5,100)']

export default function ColorBug() {
  const color = useSharedValue(colors[0])

  const toggleColor = () => {
    if (color.value === colors[0]) {
      color.value = colors[1]
    } else {
      color.value = colors[0]
    }
  }

  const style = useAnimatedStyle(() => ({
    backgroundColor: withTiming(processColor(color.value)),
  }))

  return (
    <View style={styles.container}>
      <Animated.View style={[styles.box, style]}></Animated.View>
      <Button title="toggle" onPress={toggleColor} />
    </View>
  )
}

const styles = StyleSheet.create({
  box: {
    justifyContent: 'center',
    backgroundColor: 'blue',
    height: 100,
    width: 100,
  },
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    flexDirection: 'column',
  },
})

I tried using hex strings, RGB strings, and color names in the colors array, but they all have the same result.

Here's a video of what happens:

ezgif com-gif-maker

@nandorojo
Copy link
Contributor

@jakub-gonet would you mind re-opening this?

@jakub-gonet
Copy link
Member

Sure, but I'm afraid I won't be able to help.

@jakub-gonet jakub-gonet reopened this Dec 7, 2020
@piaskowyk piaskowyk self-assigned this Dec 7, 2020
@nandorojo
Copy link
Contributor

Got it, thanks for the heads up! I'll keep an eye out here.

@dmmaslenn
Copy link

dmmaslenn commented Dec 19, 2020

Hi @nandorojo!

You should move withTiming(processColor(color.value)) from useAnimatedStyle to different method and use color.value instead, and it will work
Here is code snippet

const colors = ['rgba(5, 200, 3, 1)', 'rgba(10, 5, 100, 1)']
....
const toggleColor = () => {
    if (color.value === colors[0]) {
      color.value = withTiming(processColor(colors[1]))
    } else if (color.value === colors[1]) {
      color.value = withTiming(processColor(colors[0]))
    }
  }

  const style = useAnimatedStyle(() => ({
    backgroundColor: color.value,
  }))

@dmmaslenn
Copy link

FYI tested this case on ios, android and web
On both mobile platforms bug is reproducible
On web I just see empty screen (screenshot attached)
When moved withTiming(processColor(color.value)) from useAnimatedStyle, animation works correctly on all of the platforms

Working Code:

import Animated, {
  useSharedValue,
  withTiming,
  useAnimatedStyle,
  Easing,
  processColor,
} from 'react-native-reanimated';
import { View, Button, StyleSheet } from 'react-native';
import React from 'react';

const colors = ['rgba(5,200,3, 1)', 'rgba(10,5,100,1)']

export default function ColorBug() {
  const color = useSharedValue(colors[0])

  const toggleColor = () => {
    if (color.value === colors[0]) {
      color.value = withTiming(processColor(colors[1]))
    } else {
      color.value = withTiming(processColor(colors[0]))
    }
  }

  const style = useAnimatedStyle(() => ({
    backgroundColor: color.value,
  }))

  return (
    <View style={styles.container}>
      <Animated.View style={[styles.box, style]}></Animated.View>
      <Button title="toggle" onPress={toggleColor} />
    </View>
  )
}

const styles = StyleSheet.create({
  box: {
    justifyContent: 'center',
    backgroundColor: 'blue',
    height: 100,
    width: 100,
  },
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    flexDirection: 'column',
  },
})

image

@nandorojo
Copy link
Contributor

nandorojo commented Dec 19, 2020

@dmmaslenn Thanks for testing that, I'll try this out.

I'm actually working on a library that needs to drive the animation inside of the UAS hook, so it would be great to have this bug fixed. I really appreciate you finding the source of it.

Which version are you on, by the way?

I wonder if this could be related to #1511?

@piaskowyk
Copy link
Member

piaskowyk commented Dec 30, 2020

Hey @nandorojo I’m sorry for my late reply.
Well basically it isn't a bug 😁 When you use processColor(colorStr) it is converted to a number. In the next step you try to animate this value but this is a number not a color, and interpolation of number is different then interpolation of color. In summary why it was acting strange - because if you want to interpolate color in smooth way you should interpolate each channel (R, G, B, A) separately not as one 32bit number

You have two simple solutions:
Based on your example code. You should just remove processColor()

Code
import { View, Button, StyleSheet } from 'react-native'
import React from 'react'
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated'

const colors = ['rgb(5,200,3)', 'rgb(10,5,100)']

export default function ColorBug() {
  const color = useSharedValue(colors[0])

  const toggleColor = () => {
    if (color.value === colors[0]) color.value = colors[1]
    else color.value = colors[0]
  }

  const style = useAnimatedStyle(() => ({
    backgroundColor: withTiming(color.value),
  }))

  return (
    <View>
      <Animated.View style={[styles.box, style]}></Animated.View>
      <Button title="toggle" onPress={toggleColor} />
    </View>
  )
}

const styles = StyleSheet.create({
  box: {
    justifyContent: 'center',
    backgroundColor: 'blue',
    height: 100,
    width: 100,
  },
})

My recommended way is use interpolateColor:

Code
import { View, Button, StyleSheet } from 'react-native'
import React from 'react'
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withTiming,
  interpolateColor,
  Easing
} from 'react-native-reanimated'

const colors = ['rgb(5,200,3)', 'rgb(10,5,100)']

export default function ColorBug() {
  const color = useSharedValue(0)

  const toggleColor = () => {
    if (color.value === 0) {
      color.value = withTiming(1, {duration: 200, easing: Easing.linear})
    } else {
      color.value = withTiming(0, {duration: 200, easing: Easing.linear})
    }
  }

  const style = useAnimatedStyle(() => ({
    backgroundColor: interpolateColor(
      color.value,
      [0, 1],
      colors,
    ),
  }))

  return (
    <View>
      <Animated.View style={[styles.box, style]}></Animated.View>
      <Button title="toggle" onPress={toggleColor} />
    </View>
  )
}

const styles = StyleSheet.create({
  box: {
    justifyContent: 'center',
    backgroundColor: 'blue',
    height: 100,
    width: 100,
  },
})

I tested this on the latest version of Reanimated.

Have you any more questions or can I close this issue?

@nandorojo
Copy link
Contributor

@piaskowyk Thanks! The only open question – I'm passing a string to withTiming, but it expects a number. Should I @ts-ignore?

@piaskowyk
Copy link
Member

I think that we should change the passing type in the signature of function. I think we will change this in the newer version.

@nandorojo
Copy link
Contributor

In case anyone comes across this issue...make sure to use withTiming and not withSpring for animating colors. A spring animation for colors means flickers!

@BrandonMA
Copy link

Description

Hi! I found another bug similar to the one @nandorojo found, in fact, his library is probably buggy because of this.

Basically, animating colors works fine, as long as you don't use red😅.

The code snippet has two arrays, buggyColors and normalColors, if you try to animate buggyColors, the weird flickering occurs, the only difference is the colors inside, the first array is using red shades and the second one is using blue shades.

Something that might be useful to notice, is that using interpolateColor does work as expected, but I have a similar use case as @nandorojo where I need to update colors "on the fly", so using interpolateColor does not seem to be the best solution(also this seems to be a bug with values too close to 255).

Let me know if there's anything more I can do to help!

Code Example

import React from 'react';
import { View, Button } from 'react-native';
import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated';

const buggyColors = ['#FF1612', '#DB0D1B'];
const normalColors = ['#1281FF', '#0D63DB'];

const colors = buggyColors;

export function ColorBug() {
    const color = useSharedValue(colors[0]);

    const toggleColor = () => {
        if (color.value === colors[0]) color.value = colors[1];
        else color.value = colors[0];
    };

    const style = useAnimatedStyle(() => ({
        backgroundColor: withTiming(color.value)
    }));

    return (
        <View>
            <Animated.View style={[style, { height: 100, width: 100 }]} />
            <Button title='toggle' onPress={toggleColor} />
        </View>
    );
}

@piaskowyk
Copy link
Member

@BrandonMA I'll check it again and let you know.

@BrandonMA
Copy link

@BrandonMA I'll check it again and let you know.

Hello, I am still having this bug on the current release, where you able to reproduce it? Or am I using the library wrong?

@lieberscott
Copy link

lieberscott commented May 10, 2021

I'm getting "interpolateColor is not a function." Using v ^2.0.0-alpha.6.1.
pic00

@nandorojo
Copy link
Contributor

You should upgrade to the latest first and see if that fixes your issue.

@lieberscott
Copy link

@nandorojo Yes, just needed to upgrade, all set now, thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants