happy_story
happy_story

Reputation: 1163

Why is the speed of incrementation doubling when the function is re-triggered twice?

I am trying to understand what is causing the speed movement of the UFO image to increase once the function has been re-triggered.

var timer = null;
var el = null;
var stop = true;
var restart = false;
var stopforward = true;
var secondadd = false;

function moveit() {
  if (parseInt(el.style.left) > screen.width - 100) {
    stopforward = false;
  }
  if (stop) {
    if (stopforward) {
      el.style.left = parseInt(el.style.left) + 6 + "px";
      el.style.top = 100 + (80 * Math.sin(parseInt(el.style.left) / 50)) + "px";
    }
  }

  timer = setTimeout(moveit, 25);

  if (stopforward == false) {
    if (stop) {
      if (parseInt(el.style.left) == 0) {
        stopforward = true;
      }
      el.style.left = parseInt(el.style.left) - 6 + "px";
      el.style.top = 100 + (80 * Math.sin(parseInt(el.style.left) / 50)) + "px"
    }
  }
}

function start() {
  el = document.getElementById("img1");
  el.style.left = "0px";
  moveit();
  startstop();
}

function secondstart() {
  if (restart == true) {
    stop = true;
    moveit();
    restart = false;
  }
}

function startstop() {
  document.getElementById("start").removeEventListener("click", start, false);
  document.getElementById("start").addEventListener("click", secondstart, false);

}

document.getElementById("start").addEventListener("click", start, false);
document.getElementById("stop").onclick = function() {
  stop = false;
  restart = true;
}
#range {
  position: absolute;
  top: 0;
  left: 0;
  background: url(https://blenderartists.org/uploads/default/original/4X/7/e/2/7e2d7bea4ac21388c4a96e1371f375c4ce00094b.jpg);
  cursor: crosshair;
  height: 300px;
  width: 100%;
}

#img1 {
  position: absolute;
  border: none;
  left: 0;
  top: 100px;
  padding: 10px;
  width: 100px;
}

#start,
#stop {
  position: relative;
  top: 300px;
}
<div id="range">
  <div id="score"></div>
  <img id="img1" src="https://www.pngkit.com/png/full/779-7799339_unidentified-flying-object-saucer-cartoon-ufo-png.png">
</div>


<button id="start">START</button>
<button id="stop">STOP</button>

Sorry if it looks a bit too convoluted. There really is no other way to actually show you what happens.

When you first click start, the ufo moves at a certain speed. When you click stop, and then start again, the speed doubles. This happens for two reasons. One is because when you click start the second time, you re-trigger the moveit function. The second is because the setTimeout method that re-triggers the moveit function is never disabled even when stop is clicked, because it is outside the if condition. This results in the function being triggered twice. I can see why that would lead to one iteration being doubled in speed, but why does it remain double?

Note, please don't give me any advice on how to "fix it". I know how to fix it. This isn't my question. This is purely a curiosity based question. I just want to understand why does the speed increase. What makes it increase.

Note2, you should view it on full screen.

Upvotes: 0

Views: 115

Answers (2)

trincot
trincot

Reputation: 351328

please don't give me any advice on how to "fix it". I know how to fix it. This isn't my question.

OK.

This is purely a curiosity based question. I just want to understand why does the speed increase. What makes it increase.

So we see that when moveIt is called it will always reschedule itself to be run again, using setTimeout. This means that once you call it, you have started an infinite sequence of calls, each separated by 25ms.

When the Start button is clicked, moveIt gets called. So far so good. It's doing what it should do. The Stop button works fine too. moveIt keeps continuing its repeated calls, but the stop variable now prevents it from making any changes.

The user clicks the Start button again. Now things go wrong: moveIt gets called again. So now we have two self-propagating sequences of moveIt calls. You can see how that doubles the speed of the changes.

In short, make sure you only call moveIt once, and let setTimeout do the rest. Don't call moveIt explicitly again.

Comments

You write in comments:

I thought about that, but when I tested that scenario, I ended up dismissing it.

Then I think you did not test it correctly. So now I will provide the fix:

In the function secondstart remove the call to moveIt:

function secondstart() {
  if (restart == true) {
    stop = true;
    // moveit(); // <------- remove!
    restart = false;
  }
}

Below the version with that correction so you can run it -- I didn't change anything else:

var timer = null;
var el = null;
var stop = true;
var restart = false;
var stopforward = true;
var secondadd = false;

function moveit() {
  if (parseInt(el.style.left) > screen.width - 100) {
    stopforward = false;
  }
  if (stop) {
    if (stopforward) {
      el.style.left = parseInt(el.style.left) + 6 + "px";
      el.style.top = 100 + (80 * Math.sin(parseInt(el.style.left) / 50)) + "px";
    }
  }

  timer = setTimeout(moveit, 25);

  if (stopforward == false) {
    if (stop) {
      if (parseInt(el.style.left) == 0) {
        stopforward = true;
      }
      el.style.left = parseInt(el.style.left) - 6 + "px";
      el.style.top = 100 + (80 * Math.sin(parseInt(el.style.left) / 50)) + "px"
    }
  }
}

function start() {
  el = document.getElementById("img1");
  el.style.left = "0px";
  moveit();
  startstop();
}

function secondstart() {
  if (restart == true) {
    stop = true;
    // moveit(); // <---- removed!
    restart = false;
  }
}

function startstop() {
  document.getElementById("start").removeEventListener("click", start, false);
  document.getElementById("start").addEventListener("click", secondstart, false);

}

document.getElementById("start").addEventListener("click", start, false);
document.getElementById("stop").onclick = function() {
  stop = false;
  restart = true;
}
#range {
  position: absolute;
  top: 0;
  left: 0;
  background: url(https://blenderartists.org/uploads/default/original/4X/7/e/2/7e2d7bea4ac21388c4a96e1371f375c4ce00094b.jpg);
  cursor: crosshair;
  height: 300px;
  width: 100%;
}

#img1 {
  position: absolute;
  border: none;
  left: 0;
  top: 100px;
  padding: 10px;
  width: 100px;
}

#start,
#stop {
  position: relative;
  top: 300px;
}
<div id="range">
  <div id="score"></div>
  <img id="img1" src="https://www.pngkit.com/png/full/779-7799339_unidentified-flying-object-saucer-cartoon-ufo-png.png">
</div>


<button id="start">START</button>
<button id="stop">STOP</button>

Upvotes: 2

BiOS
BiOS

Reputation: 2304

The moveIt() method calls itself recursively. Once this process is started, it is never ending, as from within itself, the stop condition is trapped and the moveIt function is then set to behave accordingly (not moving the UFO, every 25ms).

However, in this instance, the moveIt function will keep being called. When you press start again, you invoke secondstart(), which will call moveIt() again, starting another timeout, and setting the stop variable so that the UFO moves.

The previously active timeout will now respond to this change in condition too, as setTimeout acts asynchronously. So now there will be two asyncrhonous instances of moveIt(), and if you repeat this operation there will be three, four and so on... All performing the same actions, which being CSS-based, will make your UFO move coherently, as its movement are calculated out of its instantaneous position.

I am still going to spill my two cents on what could be the quickest fix, because its fun... and might help others! So you probably guessed that already, but we need to find a way to get rid of that timeout, that you have wisely identified through the global variable timer.

You could then use the clearTimeout() method to perform clearTimeout(timer) within the secondstart() function. This way you clear the earlier timeout before starting a new one by invoking the moveIt() function again. Else... simply remove the second moveIt() call in the secondstart() function, as it is being executed already, and varying the stop variable will be enough to trigger a restart... with no adverse consequences!

Obviously, this will keep the moveIt() function being called continously even when the UFO is stopped, which maybe isn't super efficient.

var timer = null;
var el = null;
var stop = true;
var restart = false;
var stopforward = true;
var secondadd = false;

function moveit() {
  if (parseInt(el.style.left) > screen.width - 100) {
    stopforward = false;
  }
  if (stop) {
    if (stopforward) {
      el.style.left = parseInt(el.style.left) + 6 + "px";
      el.style.top = 100 + (80 * Math.sin(parseInt(el.style.left) / 50)) + "px";
    }
  }

  timer = setTimeout(moveit, 25);

  if (stopforward == false) {
    if (stop) {
      if (parseInt(el.style.left) == 0) {
        stopforward = true;
      }
      el.style.left = parseInt(el.style.left) - 6 + "px";
      el.style.top = 100 + (80 * Math.sin(parseInt(el.style.left) / 50)) + "px"
    }
  }
}

function start() {
  el = document.getElementById("img1");
  el.style.left = "0px";
  moveit();
  startstop();
}

function secondstart() {
  if (restart == true) {
    clearTimeout(timer);
    stop = true;
    moveit();
    restart = false;
  }
}

function startstop() {
  document.getElementById("start").removeEventListener("click", start, false);
  document.getElementById("start").addEventListener("click", secondstart, false);

}

document.getElementById("start").addEventListener("click", start, false);
document.getElementById("stop").onclick = function() {
  stop = false;
  restart = true;
}
#range {
  position: absolute;
  top: 0;
  left: 0;
  background: url(https://blenderartists.org/uploads/default/original/4X/7/e/2/7e2d7bea4ac21388c4a96e1371f375c4ce00094b.jpg);
  cursor: crosshair;
  height: 300px;
  width: 100%;
}

#img1 {
  position: absolute;
  border: none;
  left: 0;
  top: 100px;
  padding: 10px;
  width: 100px;
}

#start,
#stop {
  position: relative;
  top: 300px;
}
<div id="range">
  <div id="score"></div>
  <img id="img1" src="https://www.pngkit.com/png/full/779-7799339_unidentified-flying-object-saucer-cartoon-ufo-png.png">
</div>


<button id="start">START</button>
<button id="stop">STOP</button>

Upvotes: 1

Related Questions