Armin
Armin

Reputation: 1281

Animate Gradient Color React Native

I'm trying to create a gradient in react native that will start as one color when the app opens, and then gradually change into another color every 30 seconds. The regular linear gradient worked without trying to add animation. I tried using interpolation and the Animated timing as seen in the react native documentation, but nothing seems to work.

My Code:

import React, {Component} from 'react';
import {processColor, AppRegistry, StyleSheet, Dimensions, Animated, Image, Easing, View} from 'react-native';

import TimerMixin from 'react-timer-mixin';
import LinearGradient from 'react-native-linear-gradient';

var screenWidth = Dimensions.get('window').width;
var screenHeight = Dimensions.get('window').height;

//HEX version of colors
var gradientColors = [['#EF2A2A', '#EF6A2A'], //Red
['#EF6A2A', '#EFD82A'], //Orange
['#1BD170', '#61E822'], //Green
['#22D2E6', '#26F084'], //Aqua
['#2A3BEF', '#2ADCEF'], //Blue
['#EF2AD2', '#2A3BEF'], //Purple
['#EF2AD2', '#EF2A2A'] //Pink
]
var gradientColorsNoHash = [['EF2A2A', 'EF6A2A'], //Red
['EF6A2A', 'EFD82A'], //Orange
['1BD170', '61E822'], //Green
['22D2E6', '26F084'], //Aqua
['2A3BEF', '2ADCEF'], //Blue
['EF2AD2', '2A3BEF'], //Purple
['EF2AD2', 'EF2A2A'] //Pink
]
/*var gradientColors = [['ef2a2a', 'ef6a2a'], //Red
['ef6a2a', 'efd82a'], //Orange
['1bd170', '61e822'], //Green
['22d2e6', '26f084'], //Aqua
['2a3bef', '2adcef'], //Blue
['ef2ad2', '2a3bef'], //Purple
['ef2ad2', 'ef2a2a'] //Pink
]*/
//RGBA Version of Colors
/*var gradientColors = [['rgba(239, 42, 42, 1)', 'rgba(239, 106, 42, 1)'], //Red
['rgba(239, 106, 42, 1)', 'rgba(239, 216, 42, 1)'], //Orange
['rgba(0, 221, 103, 1)', 'rgba(97, 232, 35, 1)'], //Green
['rgba(34, 210, 230, 1)', 'rgba(38, 240, 132, 1)'], //Aqua
['rgba(42, 59, 239, 1)', 'rgba(42, 220, 239, 1)'], //Blue
['rgba(239, 42, 210, 1)', 'rgba(42, 59, 239, 1)'], //Purple
['rgba(239, 42, 210, 1)', 'rgba(239, 42, 42, 1)'] //Pink
]*/

function hex(c) {
  var s = "0123456789abcdef";
  var i = parseInt(c);
  if (i == 0 || isNaN(c))
    return "00";
  i = Math.round(Math.min (Math.max (0, i), 255));
  //console.log('hex(c) complete!');
  return s.charAt((i - i % 16) / 16) + s.charAt(i % 16);
}

// Convert an RGB triplet to a hex string
function convertToHex (rgb) {
  return hex(rgb[0]) + hex(rgb[1]) + hex(rgb[2]);
}

// Convert a hex string to an RGB triplet
function convertToRGB(hex) {
  var color = [];
  color[0] = parseInt(hex.substring(0, 2), 16);
  color[1] = parseInt(hex.substring(2, 4), 16);
  color[2] = parseInt(hex.substring(4, 6), 16);
  return color;
}

function generateColor(colorStart,colorEnd,colorCount) {

    // The beginning of your gradient
    var start = convertToRGB(colorStart);

    // The end of your gradient
    var end   = convertToRGB(colorEnd);

    // The number of colors to compute
    var len = colorCount;

    //Alpha blending amount
    var alpha = 0.0;

    var saida = [];

    for (i = 0; i < len; i++) {
        var c = [];
        alpha += (1.0/len);

        c[0] = start[0] * alpha + (1 - alpha) * end[0];
        c[1] = start[1] * alpha + (1 - alpha) * end[1];
        c[2] = start[2] * alpha + (1 - alpha) * end[2];

        saida.push(convertToHex(c));

    }

    return saida;

}

var number = randomIntFromInterval(0,6)
function randomIntFromInterval(min,max) { return Math.floor(Math.random()*(max-min+1)+min); }

const GradientView = React.createClass({
  mixins: [TimerMixin],

  getInitialState() {
    return {
      gradIndex: number,
      colorTop: gradientColors[number][0],
      colorBottom: gradientColors[number][1],
    }
  },
  componentDidMount() {
    this.setInterval(() => {

      var count = 0
      var topGradArray = generateColor(gradientColorsNoHash[this.state.gradIndex][0],(this.state.gradIndex === 6 ? 0 : gradientColorsNoHash[this.state.gradIndex+1][0] ),770);
      var bottomGradArray = generateColor(gradientColorsNoHash[this.state.gradIndex][1],(this.state.gradIndex === 6 ? 0 : gradientColorsNoHash[this.state.gradIndex+1][1] ),770);
      console.log('Gradients Made');
      var clearId = this.setInterval(() => {
        if (count == 0) {
          this.setState({ clearId: clearId, gradIndex: ( this.state.gradIndex === 6 ? 0 : this.state.gradIndex+1 ) });
          console.log('clearId SET!');
            }

            this.setState({
              colorTop: processColor(topGradArray[count]),
              colorBottom: processColor(bottomGradArray[count]),
            });
            count = count+1

            if (count == 769) {
              console.log('colorTop and Bottom Saved');
              this.clearInterval(this.state.clearId)
            }
          }, 13);

    }, 30000);
  },

  render(){
    return(
      <LinearGradient colors={[this.state.colorTop, this.state.colorBottom]}>
        <View style={styles.translucentContainer}/>
      </LinearGradient>
    );
  }
});

const styles = StyleSheet.create({
  translucentContainer: {
    width: screenWidth,
    height: screenHeight,
    backgroundColor: 'white',
    opacity: 0.3,
  },
});

export default GradientView;
AppRegistry.registerComponent('GradientView', () => GradientView);

UPDATE: After going through many different resources, I've come to the conclusion that the only way to animate the LinearGradient class is to change the color incrementally and rapidly like in their documentation. However, their example is continuous and doesn't allow you to set a desired final color. For my application, I want the gradient to stay one color for 30 seconds, and then go through a 10 second transition to the next color gradient, and then repeat. So for example, it would look like: Red Gradient (30 seconds), Transition Red to Orange (10 seconds), Orange Gradient (30 seconds), Transition Orange to Green (10 seconds), etc.

I'm getting two types of error with this code that seem to alternate. Generally, the first error is this one that appears when the first timer (the 30 second one) goes off:

The first error

After dismissing that error message to see what would happen, this error pops up when the same timer goes off again:

The second error

At this point I think the source of error is in generating the colors properly in the function contained in componentDidMount()

Upvotes: 8

Views: 11133

Answers (2)

AlexB
AlexB

Reputation: 3548

I found a working solution!

Use linear interpolation to generate your gradient. This is the simplest way I found to control the gradient properly.

chroma.js :

I found a library called chroma.js that can do this well ! They have a method called scale.colors that can do the job for you!

Install the package :

npm install chroma-js

You can adjust the INTERVAL and the GRADIENT_COLOR_LENGTH constants to change the effect.

Then use the generated spectrum variables in the code :

import React from 'react'
import { AppRegistry, StyleSheet, Dimensions, View } from 'react-native'

import TimerMixin from 'react-timer-mixin'
import LinearGradient from 'react-native-linear-gradient'
import Chroma from 'chroma-js'

var screenWidth = Dimensions.get('window').width
var screenHeight = Dimensions.get('window').height

const TOP_COLORS = ['#EF2A2A', '#EF6A2A', '#1BD170', '#22D2E6', '#2A3BEF', '#EF2AD2', '#EF2AD2']
const BOTTOM_COLORS = ['#EF6A2A', '#EFD82A', '#61E822', '#26F084', '#2ADCEF', '#2A3BEF', '#EF2A2A']
const GRADIENT_COLOR_LENGTH = 700
const TOP_COLORS_SPECTRUM = Chroma.scale(TOP_COLORS).colors(GRADIENT_COLOR_LENGTH)
const BOTTOM_COLORS_SPECTRUM = Chroma.scale(BOTTOM_COLORS).colors(GRADIENT_COLOR_LENGTH)
const INTERVAL = 50

const GradientView = React.createClass({
  mixins: [TimerMixin],

  getInitialState () {
    return {
      topIndex: 0,
      bottomIndex: 0,
      colorTop: TOP_COLORS_SPECTRUM[0],
      colorBottom: BOTTOM_COLORS_SPECTRUM[0]
    }
  },

  componentDidMount () {
    this.setInterval(() => {
      let { topIndex, bottomIndex } = this.state

      topIndex++
      if (topIndex === TOP_COLORS_SPECTRUM.length) {
        topIndex = 0
      }

      bottomIndex++
      if (bottomIndex === BOTTOM_COLORS_SPECTRUM.length) {
        bottomIndex = 0
      }

      this.setState({
        topIndex: topIndex,
        bottomIndex: bottomIndex,
        colorTop: TOP_COLORS_SPECTRUM[topIndex],
        colorBottom: BOTTOM_COLORS_SPECTRUM[bottomIndex]
      })
    }, INTERVAL)
  },

  render () {
    return (
      <LinearGradient colors={[this.state.colorTop, this.state.colorBottom]}>
        <View style={styles.translucentContainer} />
      </LinearGradient>
    )
  }
})

const styles = StyleSheet.create({
  translucentContainer: {
    width: screenWidth,
    height: screenHeight,
    backgroundColor: 'white',
    opacity: 0.3
  }
})

export default GradientView
AppRegistry.registerComponent('GradientView', () => GradientView)

Upvotes: 9

AlexB
AlexB

Reputation: 3548

First, make sure that you followed the steps properly to add LinearGradient to your project : https://github.com/react-native-community/react-native-linear-gradient#add-it-to-your-project

(Try rendering the component with no animation)

Second, fix your code.

You need to call setState each time to trigger a re-rendering.

The <Animated.LinearGradient ...> element you have written is invalid. It doesn't exist. Change it to a LinearGradient element.

In componentDidMount, call setInterval and change the state of the animation inside the callback.

Here is a working example from the documentation:

import React from 'react';
import {
  StyleSheet,
  Text,
  View,
} from 'react-native';
import TimerMixin from 'react-timer-mixin';
import LinearGradient from 'react-native-linear-gradient';

function incrementColor(color, step) {
  const intColor = parseInt(color.substr(1), 16);
  const newIntColor = (intColor + step).toString(16);
  return `#${'0'.repeat(6 - newIntColor.length)}${newIntColor}`;
};

const AnimatedGradient = React.createClass({
  mixins: [TimerMixin],

  getInitialState() {
    return {
      count: 0,
      colorTop: '#000000',
      colorBottom: '#cccccc',
    }
  },

  componentDidMount() {
    this.setInterval(() => {
      this.setState({
        count: this.state.count + 1,
        colorTop: incrementColor(this.state.colorTop, 1),
        colorBottom: incrementColor(this.state.colorBottom, -1),
      });
    }, 20);
  },

  render() {
    return (
      <View style={styles.container}>
        <LinearGradient
          colors={[this.state.colorTop, this.state.colorBottom]}
          style={styles.gradient} />
        <Text style={{color: this.state.colorTop}}>{this.state.colorTop}</Text>
        <Text style={{color: this.state.colorBottom}}>{this.state.colorBottom}</Text>
      </View>
    );
  }
});

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  gradient: {
    width: 200,
    height: 200,
  },
});

export default AnimatedGradient;

Upvotes: 1

Related Questions