I originally used setInterval() to make a looped image background by having two images that one starts at x:0 and another starts at x: imageWidth, then update them in the following way:
_updateBackgroundImage = () => {
this.setState({
background1Left: this.state.background1Left > (-this.backgroundImageWidth) ? this.state.background1Left-3 : this.backgroundImageWidth,
background2Left: this.state.background2Left > (-this.backgroundImageWidth) ? this.state.background2Left-3 : this.backgroundImageWidth,
})
}
It worked just fine but setInterval() was causing conflicts with another component from an online library, so I switched to using Animated API and have the following code:
this.translateValue = new Animated.Value(0)
translate() {
this.translateValue.setValue(0)
Animated.timing(
this.translateValue,
{
toValue: 1,
duration: 14000,
easing: Easing.linear
}
).start(()=>this.translate())
}
const translateBackgroundImage1 = this.translateValue.interpolate({ inputRange: [0, 1], outputRange: [0, -this.backgroundImageWidth] }) const translateBackgroundImage2 = this.translateValue.interpolate({ inputRange: [0, 1], outputRange: [this.backgroundImageWidth, -this.backgroundImageWidth] }) return ( <View style={{flex:1}}> <Animated.Image style={{ flex: 1, position: 'absolute', left: translateBackgroundImage1, }} resizeMode={Image.resizeMode.cover} source={this.backgroundImage} /> <Animated.Image style={{ flex: 1, position: 'absolute', left: translateBackgroundImage2, }} resizeMode={Image.resizeMode.cover} source={this.backgroundImage} />
To apply the same logic I used for setInterval() I would have translateBackgroundImage1 to start at x:0 in the first loop and then starts at x: ImageWidth
I'm not sure how to implement this with Animated
I eventually found a solution. It's not very clean but works.
I essentially have two Animated.image loaded from the same image. Then I have a translateValue1, which controls the left position of the first image. Then based on translateValue1, we have translateValue2 that has an offset of the imagewidth. translateValue2 controls the left position of the second image.
When the first image about to exit from the screen, it will go to the far right of the screen, and at the same time the second image will be moved to the front of the first image, so we need to change the offset to -imagewidth. Therefore there's a setState method in each of the two animation functions.
Inside the constructor I have these variables:
constructor(props) {
super(props);
this.backgroundImage = require('../assets/images/graidenttPastel.jpg');
this.backgroundImageWidth = resolveAssetSource(this.backgroundImage).width;
this.translateXValue1 = new Animated.Value(-1);
this.translateXValue2 = new Animated.Value(0);
this.animationLength = 20000;
this.state = {
translateXValue2Offset: this.backgroundImageWidth,
stopAnimation: false,
}
Then I have these two functions, each controls half of the loop:
translateXFirstHalfLoop() {
this.translateXValue1.setValue(-1);
this.setState({translateXValue2Offset: this.backgroundImageWidth});
this.firstHalfLoop = Animated.timing(
this.translateXValue1,
{
toValue: -this.backgroundImageWidth,
duration: this.animationLength/2,
easing: Easing.linear
}
).start(() => {
if(this.state.stopAnimation === false) {
this.translateXSecondHalfLoop()
}
})
}
translateXSecondHalfLoop() {
this.translateXValue1.setValue(this.backgroundImageWidth);
this.setState({translateXValue2Offset: -this.backgroundImageWidth});
this.secondHalfLoop = Animated.timing(
this.translateXValue1,
{
toValue: 0,
duration: this.animationLength/2,
easing: Easing.linear
}
).start(() => {
if(this.state.stopAnimation === false) {
this.translateXFirstHalfLoop()
}
})
}
Finally in the render() method, I have two Animated.Image like below:
render() {
this.translateXValue2 = Animated.add(this.translateXValue1, this.state.translateXValue2Offset);
return (
<SafeAreaView
style={[{backgroundColor: THEME_COLOR, flex: 1}]}
forceInset={{ bottom: 'never' }}>
<Animated.Image
style={{
position: 'absolute',
left: this.translateXValue1,
}}
resizestate={Image.resizeMode.cover}
source={this.backgroundImage}
/>
<Animated.Image
style={{
position: 'absolute',
left: this.translateXValue2,
}}
resizestate={Image.resizeMode.cover}
source={this.backgroundImage}
/>
<View style={{flex:1}}>
{this._renderScreenContent()}
</View>
</SafeAreaView>
);
}
Since each half loop function calls the other, we need to stop them before this component is unmounted, so we have this additional step below:
//clean up animation first
this.setState({stopAnimation: true}, () => {
this.props.navigation.goBack()
})
If you are looking to animate an ImageBackground, try
var AnimatedImage = Animated.createAnimatedComponent(ImageBackground)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With