Rotation is a style transform and in RN, you can rotate things like this
render() {
return (
<View style={{transform:[{rotate: '10 deg'}]}}>
<Image source={require('./logo.png')} />
</View>
);
}
However, to animate things in RN, you have to use numbers, not strings. Can you still animate transforms in RN or do I have to come up with some kind of sprite sheet and change the Image src at some fps?
You can actually animate strings using the interpolate method. interpolate takes a range of values, typically 0 to 1 works well for most things, and interpolates them into a range of values (these could be strings, numbers, even functions that return a value).
What you would do is take an existing Animated value and pass it through the interpolate function like this:
spinValue = new Animated.Value(0);
// First set up animation
Animated.timing(
this.spinValue,
{
toValue: 1,
duration: 3000,
easing: Easing.linear, // Easing is an additional import from react-native
useNativeDriver: true // To make use of native driver for performance
}
).start()
// Next, interpolate beginning and end values (in this case 0 and 1)
const spin = this.spinValue.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
})
Then use it in your component like this:
<Animated.Image
style={{transform: [{rotate: spin}] }}
source={{uri: 'somesource.png'}} />
In case if you want to do the rotation in loop, then add the Animated.timing in the Animated.loop
Animated.loop(
Animated.timing(
this.spinValue,
{
toValue: 1,
duration: 3000,
easing: Easing.linear,
useNativeDriver: true
}
)
).start();
Don't forget to add property useNativeDriver to ensure that you get the best performance out of this animation:
// First set up animation
Animated.timing(
this.state.spinValue,
{
toValue: 1,
duration: 3000,
easing: Easing.linear,
useNativeDriver: true
}
).start();
A note for the newbies like me:
For animating something else you need to wrap it in <Animated.SOMETHING> for this to work. Or else the compiler will panic on that transform property:
import {Animated} from 'react-native';
...
//animation code above
...
<Animated.View style={{transform: [{rotate: spinValue}] }} >
<YourComponent />
</Animated.View>
BUT for an image (Animated.Image), the example above is 100% goodness and correct.
Since most of the answers are functions & hooks based, herewith a complete example of class based Animation of Image.
import React from 'react';
import {
SafeAreaView,
View,
Animated,
Easing,
TouchableHighlight,
Text,
} from 'react-native';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
rotateValueHolder: new Animated.Value(0)
};
}
componentDidMount = () => {
this.startImageRotateFunction();
}
startImageRotateFunction = () => {
Animated.loop(Animated.timing(this.state.rotateValueHolder, {
toValue: 1,
duration: 3000,
easing: Easing.linear,
useNativeDriver: false,
})).start();
};
render(){
return(
<SafeAreaView>
<View>
<Animated.Image
style={{
width: 200,
height: 200,
alignSelf:"center",
transform:
[
{
rotate: this.state.rotateValueHolder.interpolate(
{
inputRange: [0, 1],
outputRange: ['0deg', '360deg'],
}
)
}
],
}}
source={{uri:'https://raw.githubusercontent.com/AboutReact/sampleresource/master/old_logo.png',}}
/>
<TouchableHighlight
onPress={() => this.startImageRotateFunction()}>
<Text style={{textAlign:"center"}}>
CLICK HERE
</Text>
</TouchableHighlight>
</View>
</SafeAreaView>
);
}
}
Just gonna drop the solution I solved by stitching together parts from the answers here.
import { Feather } from '#expo/vector-icons'
import * as React from 'react'
import { TextStyle, Animated, Easing } from 'react-native'
import { Colors, FontSize } from '~/constants/Theme'
export const LoadingSpinner = React.memo(
({ color = Colors['sand'], size = FontSize['md'] - 1, fadeInDelay = 1000, ...props }: Props) => {
const fadeInValue = new Animated.Value(0)
const spinValue = new Animated.Value(0)
Animated.sequence([
Animated.delay(fadeInDelay),
Animated.timing(fadeInValue, {
toValue: 1,
duration: 1500,
easing: Easing.linear,
useNativeDriver: true,
}),
]).start()
Animated.loop(
Animated.timing(spinValue, {
toValue: 360,
duration: 300000,
easing: Easing.linear,
useNativeDriver: true,
})
).start()
return (
<Animated.View
style={{
opacity: fadeInValue,
transform: [{ rotate: spinValue }],
}}
>
<Feather
name="loader"
size={size}
style={{
color,
alignSelf: 'center',
}}
{...props.featherProps}
/>
</Animated.View>
)
}
)
type Props = {
color?: TextStyle['color']
size?: number
featherProps?: Partial<Omit<React.ComponentProps<typeof Feather>, 'style'>>
fadeInDelay?: number
}
Hope it helps 👍
Related
I don't know why but my react-spring only works when there is a Canvas from react-three-fiber.
import React from 'react';
import { Canvas } from "#react-three/fiber";
import { useSpring, animated } from "react-spring";
// These are from the react-spring website so It does work.
function LoopTrue() {
const styles = useSpring({
loop: true,
from: { rotateZ: 0 },
to: { rotateZ: 180 },
});
return (
<animated.div
style={{
width: 80,
height: 80,
backgroundColor: "#46e891",
borderRadius: 16,
...styles,
}}
/>
);
}
function Angle() {
const [flip, set] = useState(false);
const props = useSpring({
reset: true,
reverse: flip,
from: { transform: "rotateX(0deg)" },
transform: "rotateX(180deg)",
delay: 200,
onRest: () => set(!flip),
});
return <animated.h1 style={props}>angle</animated.h1>;
}
function Project() {
return (
<>
<Angle/>
<LoopTrue/>
{/* <Canvas></Canvas> */}
</>
);
}
If I uncomment that <Canvas></Canvas> it works, but if I comment Canvas, it does not work.
I don't know if that has to do with other react-router-dom page. (I am using some react-three-fiber Canvas in "home" page", this will be the "blog" page)
Help!
This is a known bug of react-spring see here
However, as the issue describes a workaround is this:
Globals.assign({
frameLoop: 'always',
})
I recently started trying to use animation in an app I am trying to make in React Native.
I am not 100% familiar with React Animated, but I believe the animations that I am trying to make are very simple.
I have a screen where I would like some text to slide in from the right, pause a few seconds, slide out to the left, before repeating itself with some other text.
While I did manage to do that, the animation and the text quickly become very buggy (aka no smooth animation, no animation at all after a while, the text will very quickly change randomly, etc...).
I am not sure why this is, I tried switching the useNativeDriver to true to hopefully get a smoother animation, but then I get an error saying I can't use the style property 'left'.
Here is the code:
import React, { useState, useCallback, useEffect } from 'react';
import { Text, View, StyleSheet, Animated } from 'react-native';
import Constants from 'expo-constants';
function App() {
let [wordsAnim] = useState(new Animated.Value(60)),
runAnimation = () => {
wordsAnim.setValue(60);
Animated.sequence([
Animated.timing(wordsAnim, {
toValue: 0,
duration: 1500,
useNativeDriver: false,
}),
Animated.timing(wordsAnim, {
toValue: -60,
duration: 1500,
delay: 3000,
useNativeDriver: false,
}),
]).start(({ finished }) => {
runAnimation();
updateWord();
});
};
//An array of random words to display
const words = ['First', 'Second', 'Third', 'Fourth'];
//First word displayed is a random word from the array
const [word, changeWord] = useState(
words[Math.floor(Math.random() * words.length)]
);
//Update the word displayed with another random word from the array
const updateWord = () => {
const index = Math.floor(Math.random() * words.length);
changeWord(words[index]);
};
useEffect(() => {
runAnimation();
});
return (
<View style={styles.container}>
<Animated.Text
style={{
margin: 24,
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
left: wordsAnim.interpolate({
inputRange: [0, 100],
outputRange: ['0%', '100%'],
}),
}}>
{word}
</Animated.Text>
</View>
);
}
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
You can also find this example here.
I would also take any tips regarding animations or anything else for React Native,
Thanks!!
As pointed out by #David784 , my runAnimation was retriggered after every completed render. It should've only be run on component mounts.
Adding a few callbacks did the trick!
Working code below:
import React, { useState, useCallback, useEffect } from 'react';
import { Text, View, StyleSheet, Animated } from 'react-native';
import Constants from 'expo-constants';
function App() {
let [wordsAnim] = useState(new Animated.Value(60)),
runAnimation = useCallback(() => {
wordsAnim.setValue(60);
Animated.sequence([
Animated.timing(wordsAnim, {
toValue: 0,
duration: 1500,
useNativeDriver: false,
}),
Animated.timing(wordsAnim, {
toValue: -60,
duration: 1500,
delay: 3000,
useNativeDriver: false,
}),
]).start(({ finished }) => {
updateWord();
runAnimation();
});
}, [updateWord, wordsAnim]);
//An array of random words to display
const words = ['First', 'Second', 'Third', 'Fourth'];
//First word displayed is a random word from the array
const [word, changeWord] = useState(
words[Math.floor(Math.random() * words.length)]
);
//Update the word displayed with another random word from the array
const updateWord = useCallback(() => {
const index = Math.floor(Math.random() * words.length);
changeWord(words[index]);
}, [words]);
useEffect(() => {
runAnimation();
}, [runAnimation]);
return (
<View style={styles.container}>
<Animated.Text
style={{
margin: 24,
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
left: wordsAnim.interpolate({
inputRange: [0, 100],
outputRange: ['0%', '100%'],
}),
}}>
{word}
</Animated.Text>
</View>
);
}
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
I am new to react native animated feature. I am hoping to use it to fade in and out elements on button press. The below code works to fade in the element when the page loads, but the button to fade it out does not fade it out as I would expect. Can someone explain what I am doing wrong. Thank you.
class FadeComponent extends Component {
constructor(props) {
super(props)
this.state = {
fadeAnim: new Animated.Value(0), // Initial value for opacity: 0
}
this.fadeOut = this.fadeOut.bind(this);
}
componentDidMount() {
Animated.timing( // Animate over time
this.state.fadeAnim, // The animated value to drive
{
toValue: 1, // Animate to opacity: 1 (opaque)
duration: 2000, // 2000ms
}
).start(); // Starts the animation
}
fadeOut(){
this.setState({fadeAnim: new Animated.Value(1)})
Animated.timing( // Animate over time
this.state.fadeAnim, // The animated value to drive
{
toValue: 0, // Animate to opacity: 1 (opaque)
duration: 2000, // 2000ms
}
).start(); // Starts the animation
}
render() {
let { fadeAnim } = this.state;
return (
<View style = {{ backgroundColor: '#1a8cff', marginTop: 100 }}>
<Animated.View style={{ ...this.props.style, opacity: fadeAnim }} >
{this.props.children}
<View style = {{ backgroundColor: '#000000', height: 50, width: 50 }}>
</View>
</Animated.View>
<TouchableOpacity onPress={this.fadeOut} >
<Text style={{ color: 'white', textDecorationLine: 'underline', marginTop: 10 }}>
fade out
</Text>
</TouchableOpacity>
</View>
);
}
}
Because function setState is asynchronous. This should fix your issue:
this.setState({ fadeAnim: new Animated.Value(1) },
() => {
Animated.timing( // Animate over time
this.state.fadeAnim, // The animated value to drive
{
toValue: 0, // Animate to opacity: 1 (opaque)
duration: 2000, // 2000ms
}
).start();
})
I am trying to spin an Image it is basically to show that a coin is flipped ( coin Tossing animation ) I have applied this basic animation to the image but it is not getting animated,
The image is stationary while I tested it on emulator
this is my index.android.js file :
import React, { Component } from 'react';
import {
AppRegistry,
View,
Animated,
Easing
} from 'react-native';
export default class animateTest extends Component {
constructor(props) {
super(props);
this.spinValue = new Animated.Value(0);
}
spin() {
this.spinValue.setValue(0);
Animated.timing(
this.spinValue, {
toValue: 1,
duration: 1500,
useNativeDriver: true,
easing: Easing.linear
}
).start();
}
render() {
const spin = this.spinValue.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
});
return (
<View style={styles.ViewStyle}>
<Animated.Image
style={[
styles.coinStyle,
{
transform: [
{ rotate: spin }
]
}
]}
source={require('./Images/Coin_Tail.png')}
style={styles.coinStyle} />
</View>
);
}
}
const styles = {
coinStyle: {
width: 150,
height: 150,
},
ViewStyle: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'black'
}
};
AppRegistry.registerComponent('animateTest', () => animateTest);
You code have 2 issues:
1) In your render function, you have a duplicated style prop for your image that override the first style with transform styling. To fix it, remove the second style prop
2) Your code did not trigger the spin animation, you can add a touchable with on press event to call your spin method. For quick test, you can add
componentDidMount() {
this.spin();
}
I'm having problem while trying to do animations on react native
it throws an error that says I can't assign the x value. Anybody knows what can it be?
undefined is not an object (evaluating '_this.state.animateXY.xinterpolate')
import {
View,
Text,
StyleSheet,
Animated,
Image,
Easing,
TouchableHighlight,
Dimensions,
} from 'react-native';
const {width, height} = Dimensions.get('window');
class AddPost extends Component {
constructor(){
super()
this.state ={
animate: new Animated.Value(30),
animateXY: new Animated.Value({x:0, y:0}),
// radius: new Animated.Value(0)
}
this.animateInterpolate = this.state.animateXY.x.interpolate({
inputRange: [0,150],
outputRange: [1,2]
})
}
componentWillMount(){
Animated.sequence([
Animated.timing(this.state.animateXY, {
toValue: {x: height / 2, y: 0},
duration:2000
}),
Animated.timing(this.state.animate, {
toValue: 60,
duration:2000
}),
/*Animated.timing(this.state.animate, {
toValue: 40,
duration:2000
}),*/
]).start()
}
render() {
return (
<TouchableHighlight onPress={onPress}>
<View style={styles.container}>
<Animated.View style={{
width: this.state.animate,
height: this.state.animate,
backgroundColor: 'blue',
position: 'absolute',
top: this.state.animateX.x,
left: this.state.animateY.y,
// transform:[{scale: this.animateInterpolate}],
borderRadius: this.state.radius
}}/>
</View>
</TouchableHighlight>
);
}
}
this.state.animateX is simply not declared.
You need to set animatedXY to 0 : animateX: new Animated.Value(0)
Then set your animation :
Animated.timing(this.state.animateX, {
toValue: height/2,
duration:2000
}),
Then you can set the value of your X :
top: this.state.animateX
Use Animated.ValueXY insted of Animated.Value
animateX : new Animated.ValueXY({x: 0, y: 0});