Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a looped image background in React Native with Animated

Tags:

react-native

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

like image 592
bleepmeh Avatar asked Feb 02 '26 08:02

bleepmeh


2 Answers

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()
})
like image 161
bleepmeh Avatar answered Feb 03 '26 23:02

bleepmeh


If you are looking to animate an ImageBackground, try

var AnimatedImage = Animated.createAnimatedComponent(ImageBackground)
like image 25
ehacinom Avatar answered Feb 04 '26 01:02

ehacinom