How to create a shaking/floating animation to an image (react native) - 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,
},

Related

React Native how to rotate and translate at the same time

How do you rotate and translate at the same time?
In the sample below I was expecting the square to move to right and rotate about its own axis maintaining the same central y axis. If you can imagine a wheel travelling along a road
Instead, it flys off wildly
Any help would be appreciated
import React, { Component } from "react";
import {
AppRegistry,
StyleSheet,
Text,
View,
Animated
} from "react-native";
class App extends Component {
componentWillMount () {
this._animatedValue = new Animated.Value(0);
}
componentDidMount () {
Animated.timing(this._animatedValue, {
toValue: 100,
duration: 3000
}).start();
}
render () {
const interpolatedRotateAnimation = this._animatedValue.interpolate({
inputRange: [0, 100],
outputRange: ['0deg', '360deg']
});
const interpolatedRotateX = this._animatedValue.interpolate({
inputRange: [0, 100],
outputRange: [0, 200]
});
return (
<View style={styles.container}>
<Animated.View
style={[styles.box, {transform: [
{rotate: interpolatedRotateAnimation},
{translateX:interpolatedRotateX}
]}
]}
/>
</View>
);
}
};
const styles = StyleSheet.create({
container: {
flex: 1
},
box: {
backgroundColor: 'red',
position: 'absolute',
top: 100,
left: 100,
width: 100,
height: 100
}
});
export default App
What I was missing was that the axis was the center of the object.
The translate is relative to axis of the own object which is changing due to the rotation.
Translate x/y is not simply moving the object relative to the screen but relative to its own axis.

Update color of animated view after animation

I am creating a react native application and want to change the background color of an animated View after the user touches it. For more context, the view is in the shape of a square and I am rotating it 225 degrees when the component mounts. If the user touches the square, it will animate a flipping motion and show the other side of the square, which is a different color. The code I am using to do this can be seen below:
const app = (props) => {
let colors = [color1, color2, color3, ...];
let index = 0;
let animatedValue= new Animated.Value(0);
let squareSpin = new Animated.Value(0);
let val = 0;
useEffect(() => {
Animated.timing(squareSpin, {
toValue: 1,
duration: 3000,
easing: Easing.linear,
}).start();
}, []);
const startSpin = squareSpin.interpolate({
inputRange: [0, 1],
outputRange: ["0deg", "225deg"],
});
animatedValue.addListener(({ value }) => {
val = value;
});
let frontInt= animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ["0deg", "180deg"],
});
let backInt = animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ["180deg", "360deg"],
});
let opacityFront = animatedValue.interpolate({
inputRange: [89, 90],
outputRange: [1, 0],
});
let opacityBack = animatedValue.interpolate({
inputRange: [89, 90],
outputRange: [0, 1],
});
const flip= () => {
if (val>= 90) {
Animated.spring(animatedValue, {
toValue: 0,
friction: 6,
tension: 8,
}).start();
} else {
Animated.spring(animatedValue, {
toValue: 180,
friction: 6,
tension: 8,
}).start();
}
};
const frontAnimated = {
transform: [{ rotateY: frontInt }],
};
const backAnimated = {
transform: [{ rotateY: backInt}],
};
return (
<Animated.View
style={{transform: [{ rotate: startSpin }] }}
>
<TouchableWithoutFeedback
onPress={() => {
index++;
flip();
}}>
<Animated.View
style={[
styles.shape,
{
backgroundColor:
colors[index % colors.length],
},
frontAnimated ,
{ opacity: opacityFront },
]}
>
</Animated.View>
<Animated.View
style={[
styles.shape,
{
backgroundColor:
colors[(index + 1) % colors.length],
},
{ position: "absolute" },
backAnimated ,
{ opacity: opacityBack },
]}
></Animated.View>
</TouchableWithoutFeedback>
<Animated.View>
)
}
The Problem: The animations all work great, but the issue is that each side of the square I am flipping can only take on one color. Notice how in the colors array, there are multiple colors that the square should be based on the number of times the user presses the square. However, this is not happening and each side of the square is always the color is started out to be (color1 for the top side of the square and color2 for the bottom side of the square). I think this is happening because the view does not realize that the index is changing because it is never rendered again. Or maybe it simply cannot chance its color due to some properties of Animated.View, I am not really sure. I tried forcing a render when the square is pressed using useState but that resulted in the square to undo its rotation that happened when the component was mounted, which I do not want to happen. How do I get the background color of the views to change based on the number of taps by the user?
Thanks!
I was able to fix this by using two different color interpolation values.
let colorSideOne = colorValue1.interpolate({
inputRange: [...Array(colors.length)].map((_, index) => index),
outputRange: colors,
});
let colorSideTwo = colorValue2.interpolate({
inputRange: [...Array(colors.length)].map((_, index) => index),
outputRange: colors,
});
and with these values set color to the background of the card
<Animated.View
style={[
styles.shape,
{
backgroundColor: colorSideOne,
},
frontAnimated,
{ opacity: opacityFront },
]}>
</Animated.View>
<Animated.View
style={[
styles.shape,
{
backgroundColor: colorSideTwo,
},
frontAnimated,
{ opacity: opacityFront },
]}>
</Animated.View>
You now need to just properly update the colorValues depending on the index.
Note that you need to do this alternately for the front and back values
<TouchableWithoutFeedback
style={{
borderWidth: 2,
borderColor: 'red',
}}
onPress={() => {
index++;
//side = side === 'front' ? 'back' : 'front';
//console.log('side',side);
// console.log('index',index, 'color length', colors.length);
if (index & 1) {
colorValue1.setValue((index + 1) % colors.length);
} else {
colorValue2.setValue((index + 1) % colors.length);
}
// console.log('color value', colorValue1, colorValue2);
flip();
}}>
....
....
For the clarity iam attaching this expo demo
Hope it is what you are expecting !
Inside render method
const BackgroundColorConfig = this.Animation.interpolate(
{
inputRange: [ 0, 0.2, 0.4, 0.6, 0.8, 1 ],
outputRange: [ '#f6f6f6', '#f6f6f6', '#f6f6f6', LIGHT_RED_COLOR, LIGHT_RED_COLOR, LIGHT_RED_COLOR ]
});
usage backgroundColor:BackgroundColorConfig
give above attribute to your component(it should be under Animated.View tag)
Make a function to call Animation and use it either on componentDidMount or on button click
Define this.Animated = new Animated.value(0)
in constructor
StartBackgroundColorAnimation = (value) =>
{
Animated.timing(
this.Animation,
{
toValue: value,
duration: 600
}
).start();
}

How to display the frames of several pictures as an animation on loop using React Native

I have several frames of an animation. I want to display the animation on a loop. I've read:
https://reactnative.dev/docs/animations
https://reactnative.dev/docs/animated
https://blog.bitsrc.io/making-animations-in-react-native-the-simplified-guide-6580f961f6e8
And I have tried implementing:
https://medium.com/react-native-training/react-native-animations-using-the-animated-api-ebe8e0669fae
But none of them cover multiple frames of animation and how to loop through them using simple code. I'm sure what I need to do is v simple but these tutorials are over-whelming.
Here's some code I'm using:
Just before the render
Images: [
{ id: 1, src: './assets//frame1.png', title: 'foo', description: 'bar' },
{ id: 2, src: './assets//frame2.png', title: 'foo', description: 'bar' },
{ id: 3, src: './assets//frame3.png', title: 'foo', description: 'bar' },
{ id: 4, src: './assets//frame4.png', title: 'foo', description: 'bar' },
{ id: 5, src: './assets//frame32.png', title: 'foo', description: 'bar' },
]
render() {
const items = this.state.Images.map((item, key) =>
<Image key={item.id}>{item.name}</Image>
...
<View>
{items}
</View>
That doesn't work - objects are not valid as a react child...
How would I simply display the first image of that array in the first place but then make it loop though each image (creating an animation).
Can anyone provide a simple block of code that demonstrates how to cycle/loop through several .png files in an assets folder as an animation on screen?
T
All you needed Interpolation through Opacity.
Just modify the data array like your Image array and display the images inside the Animating View.
Iterate through your Image array and set the opacity Values.
const data = ['red', 'green', 'blue', 'violet', 'pink', 'red'];
this.animations = new Animated.Value(0);
this.opacity = [];
data.map((item, index) => {
this.opacity.push(
this.animations.interpolate({
inputRange: [index - 1, index, index + 1],
outputRange: [0, 1, 0],
}),
);
});
Now this.opacity array will contain the corresponding opacity values for each item.
Now start the loop. (here I am using 2 sec to animate from one image to other )
Animated.loop(
Animated.timing(this.animations, {
toValue: length - 1,
duration: 2000 * length,
easing: Easing.linear,
useNativeDriver: true,
}),
).start();
Set opacity for each item inside the render
const opacity = this.opacity[index];
Full Code (example)
import React, {Component} from 'react';
import {View, StyleSheet, Animated, Easing} from 'react-native';
const data = ['red', 'green', 'blue', 'violet', 'pink', 'red'];
const length = data.length;
export default class App extends Component {
constructor() {
super();
this.animations = new Animated.Value(0);
this.opacity = [];
data.map((item, index) => {
this.opacity.push(
this.animations.interpolate({
inputRange: [index - 1, index, index + 1],
outputRange: [0, 1, 0],
}),
);
});
}
componentDidMount() {
Animated.loop(
Animated.timing(this.animations, {
toValue: length - 1,
duration: 2000 * length,
easing: Easing.linear,
useNativeDriver: true,
}),
).start();
}
render() {
return (
<View style={styles.container}>
{data.map((item, index) => {
const opacity = this.opacity[index];
return (
<Animated.View
style={[styles.item, {backgroundColor: item, opacity}]}
/>
);
})}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
item: {
height: 200,
width: 200,
position: 'absolute',
},
});
I hope it will help you.

How to trigger a speed up (decrease the duration) of an Animated.timing in React Native?

I have a 3 second opacity reveal over a loading image.
The animation starts with onLoadStart. onLoadEnd I'd like to quickly finish the animation regardless of the duration remaining.
i.e. - If there's 2 seconds left in the animation and the image loads I'd like finish the animation in 200ms. How?
Here's the code so far. Thanks.
import React from 'react';
import { Animated, ImageBackground } from 'react-native';
const ProgressiveImage = ({ source }) => {
const opacity = new Animated.Value(0);
const startOpacityAnimation = () =>
Animated.timing(opacity, {
toValue: 1,
duration: 3000,
}).start();
const endOpacityAnimation = () => {
// Take the current opacity value and
// finish the animation in 200ms.
};
const animatedOpacity = {
backgroundColor: opacity.interpolate({
inputRange: [0, 1],
outputRange: ['rgba(255, 255, 255, 0.4)', 'rgba(255, 255, 255, 0)'],
}),
};
return (
<ImageBackground
style={{ height: 100, width: 100 }}
source={source}
onLoadStart={startOpacityAnimation}
onLoadEnd={endOpacityAnimation}
>
<Animated.View style={{ flex: 1, ...animatedOpacity }} />
</ImageBackground>
);
}
You can try with:
const endOpacityAnimation = () => {
opacity.stopAnimation()
Animated.timing(opacity, {
toValue: 1,
duration: 200
}).start()
};
Or, if you want the current value of opacity in the stop animation:
opacity.stopAnimation((value) => {
if (value < 'some number which means only 1 seconds have passed') {
Animated.timing(opacity, {
toValue: 1,
duration: 200,
}).start();
} else {
// something if have less than 2 seconds to end the animation
}
});

React Native Infinite Repeating Images Animation Not in Sync

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