title: React Native Infinite Repeating Images Animation Not in Sync
body: <p>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.</p>
  
  <p>Overall, I wrote code which creates three animations in this order:</p>
  
  <ol>
  <li>Move the image component y-offset from its initial position to 0.</li>
  <li>Move the image component y-offset from 0 to -image.height.</li>
  <li>Move the image component y-offset instantly to the original sum of all image components.</li>
  <li>Move the image component y-offset to 0 again.</li>
  <li>Move the image component y-offset to -image.height again.</li>
  </ol>
  
  <p>I put animation sequences 3-5 in a loop so they repeat indefinitely.</p>
  
  <p>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.</p>
  
  <p>I have created <a href="https://snack.expo.io/@andreyonadam/aW5maW" rel="nofollow noreferrer">this Expo</a> project to demonstrate what is happening.</p>
  
  <p>Here is a screenshot of the symptom:
  <a href="https://i.stack.imgur.com/cS4QL.jpg" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/cS4QL.jpg" alt="enter image description here"></a></p>
  
  <p>Here is my current code:</p>
  
  <p>App.js</p>
  
  <pre><code>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 (
        &lt;View style={styles.container}&gt;
          &lt;ScrollingBackground style={styles.scrollingBackground} images={[require('./assets/chess.png'),require('./assets/chess.png'),require('./assets/chess.png')]}/&gt;
        &lt;/View&gt;
      );
    }
  }
  
  const styles = StyleSheet.create({
    container: {
      flex: 1,
      justifyContent: 'center',
      backgroundColor: '#ecf0f1',
    },
    scrollingBackground: {
      width: '100%',
      height: '100%',
      backgroundColor: 'blue',
    },
  });
  </code></pre>
  
  <p>AssetExample.js</p>
  
  <pre><code>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 =&gt; {
        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 &lt; arrayLength; i++) {
        // let height  = -1 * this.state.imageComponents[i].height
        // this.state.imageComponents[i].topPositionAnimated.addListener(({value}) =&gt; 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(
          &lt;Animated.Image
            key={imageComponent.currentKey}
            source={imageComponent.image}
            style={{
              position: "absolute",
              width: "100%",
              height: imageComponent.height,
              transform: [
                {
                  translateY: imageComponent.topPositionAnimated
                }
              ],
              backgroundColor: "white"
            }}
          /&gt;
        );
      }
        return (
          &lt;View
            style={[
              styles.container,
              { backgroundColor: this.props.style.backgroundColor }
            ]}
          &gt;
            {elements}
          &lt;/View&gt;
        );
    }
  }
  
  const styles = StyleSheet.create({
    container: {
      flex: 1,
      width: "100%",
      height: "100%"
    }
  });
  </code></pre>