user9254958
user9254958

Reputation:

How to move element from left to right, turn back and repeat, using JavaScript?

I am creating a plane animation using plain JavaScript.

There is a class Plane with methods flyRight() and flyLeft(). In both of those class methods I am using setInterval(fucntion() { ... }, 1) to move the plane every 1 milisecond either to the left or to the right.

I have a problem ensuring that after executing myPlane.flyRight(), it doesn't execute the myPlane.flyLeft() - problematic lines in the snippet marked with a comment // DOES NOT WORK.

class Plane {
  constructor(htmlId, speed) {
    this.plane = document.getElementById(htmlId);     // plane HTML element
    this.width = parseInt(document.getElementById(htmlId).offsetWidth);     // plane's width
    this.speed = speed;   // pixels per milisecond
    this.range = parseInt(window.innerWidth);     // plane's range
  }
  
  flyLeft() {
    var minLeftPos = 0 - this.width - 10;
    var planeSpeed = this.speed;
    if (this.plane.style.left === '') {
      this.plane.style.left = 0
    }
    
    var moveLeft = setInterval(function() {
      if (parseInt(this.plane.style.left) >= minLeftPos) {
        this.plane.style.left = parseInt(this.plane.style.left) - planeSpeed + 'px';
      } else {
        clearInterval(moveLeft);
        this.plane.style.transform = 'rotate(180deg)';    // turns around
        this.flyRight();     // DOES NOT WORK
      }
    }, 1)
  }
  
  flyRight() {
    var maxLeftPos = this.range + this.width + 10;
    var planeSpeed = this.speed;
    if (this.plane.style.left === '') {
      this.plane.style.left = 0
    }

    var moveRight = setInterval(function() {
      if (parseInt(this.plane.style.left) <= maxLeftPos) {
        this.plane.style.left = parseInt(this.plane.style.left) + planeSpeed + 'px';
      } else {
        clearInterval(moveRight);
        this.plane.style.transform = 'rotate(180deg)';     // turns around
        this.flyLeft();     // DOES NOT WORK
      }
    }, 1)
  }
  
  fly() {
    this.flyRight();
  }
}

myPlane = new Plane("plane", 3);
myPlane.fly();
html, body {
  overflow: hidden;
}


.plane {
  width: 200px;
  height: 168px;
  position: absolute;
  top: 0;
  left: 0;
  background-image: url('https://cdn.pixabay.com/photo/2014/04/02/10/22/airplane-303639_960_720.png');
  background-position: center;
  background-size: cover;
}
<div id="plane" class="plane"></div>

Upvotes: 0

Views: 1071

Answers (3)

Einar &#211;lafsson
Einar &#211;lafsson

Reputation: 3177

Change functions to arrow functions to avoid them binding there own this. By doing so your function within the setInterval will refer this to their parent context.

class Plane {
  ...
  flyLeft () {
    ...
    var moveLeft = setInterval(() => {
      ...
    }, 1)
  }

  flyRight () {
    ...

    var moveRight = setInterval(() => {
      ...
    }, 1)
  }

  ...
}

class Plane {
  constructor(htmlId, speed) {
    this.plane = document.getElementById(htmlId);     // plane HTML element
    this.width = parseInt(document.getElementById(htmlId).offsetWidth);     // plane's width
    this.speed = speed;   // pixels per milisecond
    this.range = parseInt(window.innerWidth);     // plane's range
  }
  
  flyLeft () {
    var minLeftPos = 0 - this.width - 10;
    var planeSpeed = this.speed;
    if (this.plane.style.left === '') {
      this.plane.style.left = 0
    }
    
    var moveLeft = setInterval(() => {
      if (parseInt(this.plane.style.left) >= minLeftPos) {
        this.plane.style.left = parseInt(this.plane.style.left) - planeSpeed + 'px';
      } else {
        clearInterval(moveLeft);
        this.plane.style.transform = 'rotate(180deg)';    // turns around
        this.flyRight();     // DOES NOT WORK
      }
    }, 1)
  }
  
  flyRight () {
    var maxLeftPos = this.range + this.width + 10;
    var planeSpeed = this.speed;
    if (this.plane.style.left === '') {
      this.plane.style.left = 0
    }

    var moveRight = setInterval(() => {
      if (parseInt(this.plane.style.left) <= maxLeftPos) {
        this.plane.style.left = parseInt(this.plane.style.left) + planeSpeed + 'px';
      } else {
        clearInterval(moveRight);
        this.plane.style.transform = 'rotate(180deg)';     // turns around
        this.flyLeft();     // DOES NOT WORK
      }
    }, 1)
  }
  
  fly () {
    this.flyRight();
  }
}

myPlane = new Plane("plane", 3);
myPlane.fly();
html, body {
  overflow: hidden;
}


.plane {
  width: 200px;
  height: 168px;
  position: absolute;
  top: 0;
  left: 0;
  background-image: url('https://cdn.pixabay.com/photo/2014/04/02/10/22/airplane-303639_960_720.png');
  background-position: center;
  background-size: cover;
}
<div id="plane" class="plane"></div>

Upvotes: 0

Henfs
Henfs

Reputation: 5411

I know you are dealing with JavaScript to run this animation, but let me present you a CSS only solution using animation:

html, body {
  overflow: hidden;
}

.plane {
  transform: translate(-100%);
  animation: flyingPlane 10s linear infinite;
  width: 200px;
  height: 168px;
  position: absolute;
  top: 0;
  left: 0;
  background-image: url('https://cdn.pixabay.com/photo/2014/04/02/10/22/airplane-303639_960_720.png');
  background-position: center;
  background-size: cover;
}

@keyframes flyingPlane {
  40% {
    transform: translate(100vw);
  } 50% {
    transform: translate(100vw) scale(-1, 1);
  } 90% {
    transform: translate(-100%) scale(-1, 1);
  }
} 

Upvotes: 0

GenericUser
GenericUser

Reputation: 3230

The issue is in the bindings you have within your setInterval functions (note at the end of the functions I use .bind). Callback functions used in setInterval and setTimeout need to be bound to a scope in order to handle this.

There is a cleaner way which would involve using arrow functions instead of function(){} format. The reason that method works is arrow functions preserve the lexical scoping, which is exactly what the bind functions are doing. So by using arrow functions you don't have to use .bind at all, it comes for free.

class Plane {
  constructor(htmlId, speed) {
    this.plane = document.getElementById(htmlId);     // plane HTML element
    this.width = parseInt(document.getElementById(htmlId).offsetWidth);     // plane's width
    this.speed = speed;   // pixels per milisecond
    this.range = parseInt(window.innerWidth);     // plane's range
  }
  
  flyLeft() {
    var minLeftPos = 0 - this.width - 10;
    var planeSpeed = this.speed;
    if (this.plane.style.left === '') {
      this.plane.style.left = 0
    }
    
    var moveLeft = setInterval(function() {
      if (parseInt(this.plane.style.left) >= minLeftPos) {
        this.plane.style.left = parseInt(this.plane.style.left) - planeSpeed + 'px';
      } else {
        clearInterval(moveLeft);
        this.plane.style.transform = 'rotate(180deg)';    // turns around
        this.flyRight();     // DOES NOT WORK
      }
    }.bind(this), 1)
  }
  
  flyRight() {
    var maxLeftPos = this.range + this.width + 10;
    var planeSpeed = this.speed;
    if (this.plane.style.left === '') {
      this.plane.style.left = 0
    }

    var moveRight = setInterval(function() {
      if (parseInt(this.plane.style.left) <= maxLeftPos) {
        this.plane.style.left = parseInt(this.plane.style.left) + planeSpeed + 'px';
      } else {
        clearInterval(moveRight);
        this.plane.style.transform = 'rotate(180deg)';     // turns around
        this.flyLeft();     // DOES NOT WORK
      }
    }.bind(this), 1)
  }
  
  fly() {
    this.flyRight();
  }
}

myPlane = new Plane("plane", 3);
myPlane.fly();
html, body {
  overflow: hidden;
}


.plane {
  width: 200px;
  height: 168px;
  position: absolute;
  top: 0;
  left: 0;
  background-image: url('https://cdn.pixabay.com/photo/2014/04/02/10/22/airplane-303639_960_720.png');
  background-position: center;
  background-size: cover;
}
<div id="plane" class="plane"></div>

Upvotes: 1

Related Questions