React Native Infinite Repeating Images Animation Not in Sync - react-native

I am attempting to create an infinite parallax background animation in React Native using a set of images. I have successfully created an animation. However, it seems like the longer the animations run, the more they seem unsynced.
Overall, I wrote code which creates three animations in this order:
Move the image component y-offset from its initial position to 0.
Move the image component y-offset from 0 to -image.height.
Move the image component y-offset instantly to the original sum of all image components.
Move the image component y-offset to 0 again.
Move the image component y-offset to -image.height again.
I put animation sequences 3-5 in a loop so they repeat indefinitely.
I also have the same issue without using Expo. I also thought about having the view position not being absolute so the views would be forced to touch each other. However, with that approach, I would have to re-render when I want to switch my component order.
I have created this Expo project to demonstrate what is happening.
Here is a screenshot of the symptom:
Here is my current code:
App.js
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { Constants } from 'expo';
// You can import from local files
import ScrollingBackground from './components/AssetExample';
// or any pure javascript modules available in npm
import { Card } from 'react-native-paper';
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<ScrollingBackground style={styles.scrollingBackground} images={[require('./assets/chess.png'),require('./assets/chess.png'),require('./assets/chess.png')]}/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#ecf0f1',
},
scrollingBackground: {
width: '100%',
height: '100%',
backgroundColor: 'blue',
},
});
AssetExample.js
import React, { Component } from "react";
import {
StyleSheet,
View,
Animated,
Image,
Dimensions,
Easing
} from "react-native";
export default class ScrollingBackground extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
let imageComponents = [];
let lastImageYOffset = 0;
let counter = 0;
let deviceWidth = Dimensions.get("window").width;
this.props.images.forEach(image => {
const { width, height } = Image.resolveAssetSource(image);
let localElement = {};
let currentKey = "image" + counter.toString();
localElement.width = width;
localElement.height = (height * deviceWidth) / width;
localElement.initialOffset = lastImageYOffset;
localElement.topPositionAnimated = new Animated.Value(lastImageYOffset);
localElement.image = image;
localElement.currentKey = currentKey;
imageComponents.push(localElement);
lastImageYOffset = lastImageYOffset + localElement.height;
counter++;
});
lastImageYOffset = lastImageYOffset - imageComponents[imageComponents.length-1].height
this.setState({
imageComponents: imageComponents,
lastImageYOffset: lastImageYOffset
});
}
componentDidMount() {
let animations = [];
let arrayLength = this.state.imageComponents.length;
for (let i = 0; i < arrayLength; i++) {
// let height = -1 * this.state.imageComponents[i].height
// this.state.imageComponents[i].topPositionAnimated.addListener(({value}) => value == height ? console.log(this.state) : "");
animations.push(
Animated.sequence([
Animated.timing(this.state.imageComponents[i].topPositionAnimated, {
toValue: 0,
duration:
10 *
(this.state.imageComponents[i].initialOffset),
delay: 0,
easing: Easing.linear,
useNativeDriver: true
}),
Animated.timing(this.state.imageComponents[i].topPositionAnimated, {
toValue: -1 * this.state.imageComponents[i].height,
duration:
10 *
(this.state.imageComponents[i].height),
delay: 0,
easing: Easing.linear,
useNativeDriver: true
}),
Animated.loop(
Animated.sequence([
Animated.timing(this.state.imageComponents[i].topPositionAnimated, {
toValue: this.state.lastImageYOffset,
duration: 0,
delay: 0,
useNativeDriver: true
}),
Animated.timing(this.state.imageComponents[i].topPositionAnimated, {
toValue: 0,
duration:
10 *
(this.state.lastImageYOffset),
delay: 0,
easing: Easing.linear,
useNativeDriver: true
}),
Animated.timing(this.state.imageComponents[i].topPositionAnimated, {
toValue: -1 * this.state.imageComponents[i].height,
duration:
10 *
(this.state.imageComponents[i].height),
delay: 0,
easing: Easing.linear,
useNativeDriver: true
}),
])
)
])
);
}
Animated.parallel(animations).start();
}
render() {
let elements = [];
for (imageComponent of this.state.imageComponents) {
elements.push(
<Animated.Image
key={imageComponent.currentKey}
source={imageComponent.image}
style={{
position: "absolute",
width: "100%",
height: imageComponent.height,
transform: [
{
translateY: imageComponent.topPositionAnimated
}
],
backgroundColor: "white"
}}
/>
);
}
return (
<View
style={[
styles.container,
{ backgroundColor: this.props.style.backgroundColor }
]}
>
{elements}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
width: "100%",
height: "100%"
}
});

Due to latency issues with animating multiple views, animating a container view containing all the individual image views turned out to be the better alternative. I ended up create an react native library for this: https://www.npmjs.com/package/react-native-scrolling-images

Related

How to create a shaking/floating animation to an image (react native)

I want to create a shaking/floating (call it what you want) animation to an image.
Basically the image will float or wiggle left to right and at the same time up and down.
The animation will be triggered automatically.
This is the component I have. Image is a round ball so nothing fancy.
I tried putting values in inputRange and outputRange but nothing happened. Probably because it doesn't trigger the animation (?).
New to react native, not even sure if below is a good start to my animationgoal.
import React, { Component } from "react";
import { Animated } from "react-native";
import Images from "./assets/Images";
export default class Round extends Component {
constructor(props) {
super(props);
this.animatedValue = new Animated.Value(0);
}
render() {
let float = this.animatedValue.interpolate({
inputRange: [],
outputRange: [],
});
return (
<Animated.Image
style={{
position: "absolute",
left: x,
top: y,
width: width,
height: height,
transform: { translate: float },
}}
resizeMode="stretch"
source={Images.round}
/>
);
}
}
I used the react-native-animatable library to solve my problem
I have created something similar to iPhone delete app screen, where all icons wiggle.
You can decrease duration field to increase the speed of wiggle.
const spinValue = new Animated.Value(0);
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(spinValue, {
toValue: 1,
duration: 30,
useNativeDriver: true,
}),
Animated.timing(spinValue, {
toValue: 2,
duration: 40,
useNativeDriver: true,
}),
]),
).start();
}, [spinValue]);
const spin = spinValue.interpolate({
inputRange: [0, 1, 2],
outputRange: ['0deg', '2deg', '-2deg'],
});
**
JSX **
<
Animated.View
style = {
[
styles.container,
styles.deleteContainer,
{
transform: [{
rotate: spin
}],
},
]
} >
<
Image
style = {
[styles.thumb, styles.deleteContainer]
}
source = {
{
uri: item.thumbnailUrl
}
}
/> <
/Animated.View>
deleteContainer: {
opacity: 0.85,
borderRadius: 5,
borderColor: 'red',
borderWidth: 1.4,
},

Slide up down animation in the react native

I am using animation for up and down in the react native, But the animation just slide from up to down and then stop at the bottom i want to move it up and down continuously. I have also used animation loop so please check and provide me solution for this
import React, { useEffect, useState } from 'react'
import { Text, View, Animated, Easing, StyleSheet } from 'react-native'
import LoaderLogo from '../../icons/commonicons/LoaderLogo'
import { Loadericon } from '../../constants/Image';
import LinearGradient from 'react-native-linear-gradient';
import { dynamicSize } from '../sizechoose';
const amimationScreen = () => {
const startValue = new Animated.Value(0);
const endValue = dynamicSize(225);
const startValue2 = new Animated.Value(225);
const endValue2 = dynamicSize(0);
const duration = 5000;
useEffect(() => {
Animated.sequence([
Animated.timing(startValue, {
toValue: endValue,
duration: duration,
useNativeDriver: true,
}),
Animated.timing(startValue2, {
toValue: endValue2,
duration: duration,
useNativeDriver: true,
})
]).start()
}, [startValue, endValue, duration]);
return (
<Animated.View style={[{ transform: [{ translateY: startValue }] }]}>
<View style={{backgroundColor:'red',height:10,width:100}}>
</View>
</Animated.View>
)
}
export default amimationScreen
I also tried with react-native-animatable package but it is not good to use for me as it starts animation from the top of the screen.
This worked for me:
const App = () => {
const animated = new Animated.Value(0);
const duration = 5000;
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(animated, {
toValue: 255,
duration: duration,
useNativeDriver: true,
}),
Animated.timing(animated, {
toValue: 0,
duration: duration,
useNativeDriver: true,
}),
]),
).start();
}, []);
return (
<Animated.View style={[{transform: [{translateY: animated}]}]}>
<View style={{backgroundColor: 'red', height: 10, width: 100}}></View>
</Animated.View>
);
};
So instead of having two instances of Animated.Value for translation, create one and let it transition from 0 to 255 and from 255 back to 0 in sequence. And make it loop once the sequence has finished.
I think the main problem in your original approach is that startValue decides how the view translates since this is what you pass as the value of translateY. The downward animation therefore happens correctly in your example. The upward animation however does not happen, because startValue2 is passed to Animated.timing and startValue is not used in the translation of any views in your example.
import React, { useEffect, useRef, useState } from 'react';
import { Animated, Dimensions, Easing, StyleSheet, View } from 'react-native';
export const App = () => {
const animatedValue = useRef(new Animated.Value(0)).current;
const [isTop, setIsTop] = useState(true);
const startAnimation = toValue => {
Animated.timing(animatedValue, {
toValue,
duration: 1000,
easing: Easing.linear,
useNativeDriver: true
}).start(() => {
setIsTop(!isTop);
})
}
useEffect(() => {
startAnimation(isTop ? 1 : 0);
}, [isTop]);
const translateY = animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, Dimensions.get('window').height - 70],
extrapolate: 'clamp'
})
return (
<View style={styles.container}>
<Animated.View style={[styles.square, { transform: [{ translateY }] }]}>
</Animated.View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center'
},
square: {
width: 70,
height: 70,
backgroundColor: 'red'
}
});

Simple animation in react-native: moving an image from left to right

Can anyone share a code example for a react-native animation that moves an image from left to right, then move back to the starting point and repeat the motion?
Update:
The answer below helped a lot, but it didn't work. I used it to create the following animation that moves an image from left to right (I am using RN 0.62.2).
import React from 'react'
import { StyleSheet, View, Animated, Easing } from 'react-native';
const test = require('../images/test.png');
export class Splash extends React.Component {
constructor(props) {
super(props);
this.state = { xValue: new Animated.Value(-100) }
}
moveLR = () => {
Animated.timing(
this.state.xValue,
{
toValue: 100,
duration: 1000, // the duration of the animation
easing: Easing.linear, // the style of animation
useNativeDriver: true
}
).start();
}
moveRL = () => {
Animated.timing(
this.state.xValue,
{
toValue: -100,
duration: 3000, // the duration of the animation
easing: Easing.linear, // the style of animation
useNativeDriver: true
}
).start();
}
componentDidMount = () => {
this.moveLR();
}
render = () => {
return (
<View style={styles.mainContainer}>
<Animated.Image
style={{ width: 170, height: 146 }}
source={test}
style={{ width: 170, height: 146,
transform: [{ translateX: this.state.xValue }] }}>
</Animated.Image>
</View>
)
}
}
const styles = StyleSheet.create({
mainContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
animationView: {
backgroundColor: 'red',
width: 100,
height: 100
}
})
Update by Yossi:
The code below didn't work for me in RN 0.62.2. I am accepting the answer, but I modified it and the code that is working is included now in the question.
Original answer:
Before getting started, I need to introduce you to the two types of values ​​for Animated animations:
Animated.Value () where we define a value, useful when we want to move an element on a single axis (X or Y), change the size of an element, etc. This is what we will use here, in this chapter, and this is what is used the most.
Animated.ValueXY () where we define a vector, useful for moving an element on two axes.
With these values, we can define several types of Animated animations. We will discover them one by one, testing them each time. in this example, I will only talk about Animated.timing ()
Here you can see an example of code which is gonna moove a red box from left to right and stop when the user decides, you can try it and tell if it worked for you :
// Test.js
import React from 'react'
import { StyleSheet, View, Animated, TouchableOpacity, Text, Easing } from 'react-native'
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
leftPosition : new Animated.Value (0)
}
this.mooveLR = this.mooveLR.bind(this);
this.mooveRL = this.mooveRL.bind(this);
this.stopAnimation = this.stopAnimation.bind(this);
}
stopAnimation () {
this.setState({
leftPosition : this.state.leftPosition // this forces the left position to remain the same considering the `componentDidMount` method already happened
})
}
mooveLR (){
Animated.timing(
this.state.leftPosition,
{
toValue: 100,
duration: 3000, // the duration of the animation
easing: Easing.linear, // the style of animation
}
).start() // starts this annimation once this method is called
}
mooveRL (){
Animated.timing(
this.state.leftPosition,
{
toValue: 0,
duration: 3000, // the duration of the animation
easing: Easing.linear, // the style of animation
}
).start() // starts this annimation once this method is called
}
componentDidMount(){
this.state.leftPosition === 0 ? this.mooveLR () : this.mooveRL () // repeats always when the red box return to its initial position : leftPosition === 0
}
render() {
return (
<View style={styles.main_container}>
<Animated.View style={[styles.animation_view, {left : this.state.leftPosition}]}>
</Animated.View>
<TouchableOpacity onPress = { () => this.stopAnimation ()}>
<Text>Stop animation</Text>
</TouchableOpacity>
</View>
)
}
}
const styles = StyleSheet.create({
main_container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
animation_view: {
backgroundColor: 'red',
width: 100,
height: 100
}
})
export default Test;
Hope it's gonna help
Regards
I have resolved this issue by doing:
import {
Easing
} from 'react-native';
Since I prefer using react hooks, I converted the solution Provided by #pacy.eth, and here it is for anyone who prefers to use react hooks.
import { View, Text, Animated, Easing } from 'react-native';
import React, { useEffect, useState } from 'react';
const Animate= ({ children, left, leftP, duration }) => {
const [leftPosition, setLeftPosition] = useState(new Animated.Value (leftP));
useEffect(() => {
left ? mooveLR() : mooveRL();
}, []);
const mooveLR = () => {
Animated.timing(leftPosition, {
toValue: 100,
duration, // the duration of the animation
easing: Easing.linear, // the style of animation
}).start(); // starts this annimation once this method is called
};
const mooveRL = () => {
Animated.timing(leftPosition, {
toValue: 0,
duration, // the duration of the animation
easing: Easing.linear, // the style of animation
}).start(); // starts this annimation once this method is called
};
return (
<Animated.View style={[{ left: leftPosition }]}>{children}</Animated.View>
);
};
export default Animate;
And with a little modifications, I made it reusable in severals ways:
one of my favorite ways, is by wrapping the component that I want to animate and I pass the the direction "left: true or false" I set the "leftP" which is the leftPosition (in my case I am hiding the view and with a click of a button I slide it in with the Animate component created) and the "duration" of the animation.
for ex:
<Animate left={false} duration={1000} leftP={-Dimensions.get('window').width}>
...
</Animate>

In react-native, how can I make a View's background color change to another color, and then fade out?

I'd like the View to change to dark blue, and then slowly fade back to normal (aka white).
How can this be done?
You can use animated by react-native.
Here's a sample code to achieve what you are looking for
import * as React from "react";
import { Text, View, StyleSheet, Animated } from "react-native";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
colorAnimation: new Animated.Value(0)
};
}
componentDidMount() {
const { colorAnimation } = this.state;
{
/* Change Color To blue */
}
Animated.timing(colorAnimation, {
toValue: 255,
duration: 1000 //Animation Duration
}).start();
{
/* After 1 second change color back to white */
}
setInterval(() => {
Animated.timing(colorAnimation, {
toValue: 0,
duration: 3000 //Animation Duration
}).start();
}, 1000);
}
render() {
const interpolatedColor = this.state.colorAnimation.interpolate({
inputRange: [0, 255],
outputRange: ["rgb(255,255,255)", "rgb(0, 0, 139)"]
});
return (
<Animated.View
style={[styles.container, { backgroundColor: interpolatedColor }]}
></Animated.View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
padding: 8
}
});
You can view the demo here.

React native animation of a View from side to side

I'm working with the animation of react-native lately and I'm trying to make a View component moving from side to side by click.
This is the code I have so far that works :
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Animated,
Easing,
TouchableOpacity,
Dimensions
} from 'react-native';
export default class App extends Component {
constructor () {
super()
this.state = {
isLeftSide: true,
}
this.animatedValue = new Animated.Value(0)
}
animate () {
this.animatedValue.setValue(0);
Animated.timing(
this.animatedValue,
{
toValue: 1,
duration: 300,
easing: Easing.linear
}
).start()
}
fire = () => {
this.animate();
this.setState({isLeftSide: (!this.state.isLeftSide)});
}
direction = () => this.state.isLeftSide ? 'rtl' : 'ltr';
render() {
const screenWidth = Dimensions.get('screen').width;
const objectMaxCoord = screenWidth - 40;
const outputRange = {
rtl: [0, objectMaxCoord],
ltr: [objectMaxCoord, 0]
}
const marginLeft = this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: outputRange[this.direction()]
})
return (
<View style={styles.container}>
<TouchableOpacity onPress={() => this.fire()}><Text>Run</Text></TouchableOpacity>
<Animated.View
style={{
marginLeft,
height: 30,
width: 40,
backgroundColor: 'red'}} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
marginTop: 30
}
});
Now, the problem is that the red cube starts on the left side, but as I click run, it jumps (without animation) to the right corner and then move smoothly(in animation) to the left side. what comes after that works just perfect. Why is this first jump happens ?
And is there any easier way to make this animation done ?
( P.S I'm working on android )
Here is a link to an expo
https://snack.expo.io/S1aAgNq6Z
Probably because the state might not update right away. Try to change your fire function like this:
this.setState({isLeftSide: (!this.state.isLeftSide)}, () => {
this.animate();
});
setState accepts a callback when done.
I was playing with a Snack for a while and the issue is clearly that the state gets unsync because it says is on the left side when actually is on the right. I don't see anything else messing with the state.
You can see it here: https://snack.expo.io/BJhb4-pAW
The snack is somehow limited so can you try this on your code?
finishedAnimation = (finished) => {
if (finished)
this.setState({isLeftSide: !(this.state.isLeftSide)});
}
fire = () => {
this.animatedValue.setValue(0);
Animated.timing(
this.animatedValue,
{
toValue: 1,
duration: 300,
easing: Easing.linear
}
).start(this.finishedAnimation);
}