Maik Lowrey
Maik Lowrey

Reputation: 17556

Javascript while with time delay

My goal I want to run loop that decrements a global variable stepwise in n ms (for Example: 200ms) time intervals.

Thanks in advance!

What i already tried

I tried to use ascy await. But in combination with css transition i run in an infinite loop (In codepen.io). But here in SO you will see that it starts not running smoothly if you keep pressing arrow up.

const procentage = document.querySelector(".procentage");
const green = engine.querySelector(".green");
let number = 0;
let decrementing  = false;

window.addEventListener('keydown', (e) => {
  e = e || window.event;
  e.preventDefault();
  
  if (e.keyCode == '38') {
    console.log("accelerate");     
    actionHandler( number++ );
    decrementing = false;
    downLoop();
  }
});


function actionHandler(num) {
  procentage.innerHTML = num;
  const str = num + "%"
  green.style.width = str;
  procentage.innerHTML = str;    
}

window.addEventListener('keyup', (e) => {
  e = e || window.event;
  e.preventDefault();
  
  if (e.keyCode == '38') {
    console.log("decelerate");
    decrementing = true;
    downLoop();
  }
});


async function downLoop() {
  if (! decrementing) {
    return false
  };
  
  const timer = ms => new Promise(res => setTimeout(res, ms));
   
  while (number > 1) {
    // how to decrement ever 200ms???
    actionHandler( number-- );      
    await timer(200)
  }

}
#engine {
  background-color:black;
  height: 50px;
  position: relative;
}

p {
  text-align: center;
}

.green {
  background:green;
  height: 50px;
  width:0%;
  transition: width 0.2s;
  text-align:center;
}
.procentage {
  position:absolute;
  top: 50%;
  left: 50%;
  transform: translate(0%,-50%);
  color: white;
  fon-weight: bold;
  font-size:28px;
}
<div id="engine">
  <div><span class="procentage">0</span></div>
  <div class="green"></div>    
</div>
<p>press arrow Up</p>

Upvotes: 1

Views: 258

Answers (2)

Rickard Elim&#228;&#228;
Rickard Elim&#228;&#228;

Reputation: 7591

Whenever you animate, you shouldn't rely on setInterval or setTimeout, because that means that you will update "somewhere after X milliseconds" which will often end up in the middle of a screen repaint, and will therefor cause janky animation.

Instead, you should use RequestAnimationFrame which does a calculation before every repaint. So if you got a monitor with a framerate of 60 Hz, that means that you will do 60 repaints every second. For each repaint, check if enough time have passed since the last update (shouldTriggerUpdate() below) and then check if you should update the number.

I also added the class KeyHandler to keep track of which keys that have been pressed.

I got sloppy at the end and just added a decrement as an "else" of the if statement. You will figure something out when you get there when you want to set up more keys to be pressed.

You shouldn't use KeyboardEvent.keyCode, but instead KeyboardEvent.code.

const procentage = document.querySelector(".procentage");
const green = engine.querySelector(".green");
let number = 0;
let speed = 200      // ms
let lastUpdated = 0; // ms
let animationId = 0; // use later on to pause the animation

class KeyHandler {
  ArrowLeft  = false
  ArrowUp    = false
  ArrowRight = false
  ArrowDown  = false

  #setKey(code, value) { // private method
    if (typeof this[code] != undefined) {
      this[code] = value;
    }
  }

  set pressedKey(code) {
    this.#setKey(code, true);
  }

  set releasedKey(code) {
    this.#setKey(code, false);
  }
}

let keyHandler = new KeyHandler();

window.addEventListener('keydown', (e) => {
  e = e || window.event;
  e.preventDefault();
  keyHandler.pressedKey = e.code;
});

window.addEventListener('keyup', (e) => {
  e.preventDefault();
  keyHandler.releasedKey = e.code
});


function actionHandler(num) {
  const str = num + "%"
  green.style.width = str;
  procentage.innerHTML = str;
}

function shouldTriggerUpdate(timeInMillis) {
  let difference = timeInMillis - lastUpdated;

  return difference >= speed;
}

function planeAnimation() {
  let timeInMillis = new Date().getTime();
  
  if (shouldTriggerUpdate(timeInMillis)) {
    lastUpdated = timeInMillis;
    
    if (keyHandler.ArrowUp) {
      actionHandler(++number)
    } else if (number > 0) {
      actionHandler(--number)
    }
  }

  animationId = requestAnimationFrame(planeAnimation)
}

animationId = requestAnimationFrame(planeAnimation);
#engine {
  background-color: black;
  height: 50px;
  position: relative;
}

p {
  text-align: center;
}

.green {
  background: green;
  height: 50px;
  width: 0%;
  transition: width 0.2s;
  text-align: center;
}

.procentage {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(0%, -50%);
  color: white;
  fon-weight: bold;
  font-size: 28px;
}
<div id="engine">
  <div><span class="procentage">0</span></div>
  <div class="green"></div>
</div>
<p>press arrow up</p>

Upvotes: 1

Peter Seliger
Peter Seliger

Reputation: 13366

From the above comments ...

"Instead of incrementing each time the number value push a new async timer function, set to 200 msec delay but not immediately triggered, into an array. Create an async generator from it and iterate over the latter via the for-await...of statement where one could decrement number again." – Peter Seliger

"@PeterSeliger Hi Peter! Thank you for your comment. Can you make a small example please?" – Maik Lowrey

And here the requested demonstration.

function createWait(delay) {
  return async function wait () {
    let settle;
    const promise = new Promise((resolve) => { settle = resolve;});
    setTimeout(settle, delay, { delay, state: 'ok' });
    return promise;
  };
}
async function* getWaitIterables(list) {
  let wait;
  while (wait = list.shift()) {

    yield wait();
  }
}

// demo for ...
// - creating an async `wait` function
//   or a list of such kind.
// - creating an async generator from
//   a list of async `wait` functions.
// - iterating an async generator of
//   async `wait` functions.

const waitingList = [ //  const waitingList = [];
  2000,               //  waitingList.push(createWait(2000));
  1000,               //  waitingList.push(createWait(1000));
  3000,               //  waitingList.push(createWait(3000));
].map(createWait);    //  - The OP of cause needs to push into.

let number = 3;       //  - The incremented `number` value e.g. ... 3.

(async () => {
  for await (const { delay, state } of getWaitIterables(waitingList)) {
    --number;
    console.log({ number, delay, state });
  }
})();

console.log('... running ...', { number });
.as-console-wrapper { min-height: 100%!important; top: 0; }

Upvotes: 1

Related Questions