Image is not displaying sequentially with a delay function

VS Code

<html>
    <body>
        <div id="canvasContainer">
            <canvas id="canvas" width="900" height="520"></canvas>
            </div>
        <script src="image.js"></script>
</body>
</html>
//------------------------------
const gcCanvas = document.getElementById('canvas');
  gcCanvas.width = 600;
  gcCanvas.height = 300;

const gcContext = gcCanvas.getContext('2d');

const gcBackgroundImage = new Image();
const gcBackgroundWidth = gcCanvas.width;
let glBackgroundHeight; 

const gcCarImage = new Image();
const gcCarImageWidth = 40; 
const gcCarImageHeight = 100; 
let glCarTopLeftX; 
let glCarTopLeftY; 

let glDegrees = 0;
let glArrowFlag = false;
//------------------------------
//++++++++++++++++++++++++++++++
//------------------------------
document.addEventListener('DOMContentLoaded', (ev) => 
  {
    gcBackgroundImage.onload = function ()
    {
      gcCarImage.onload = function () 
      {
        const cAspectRatio = gcBackgroundImage.naturalWidth / gcBackgroundImage.naturalHeight;
        glBackgroundHeight = gcCanvas.height = gcBackgroundWidth / cAspectRatio; 
        gcContext.drawImage(gcBackgroundImage, 0, 0, gcBackgroundWidth, glBackgroundHeight); 
        glCarTopLeftX = gcBackgroundWidth / 2 - gcCarImageWidth / 2; 
        glCarTopLeftY = glBackgroundHeight / 2 - gcCarImageHeight / 2; 
        gcContext.drawImage(gcCarImage, glCarTopLeftX, glCarTopLeftY, gcCarImageWidth, gcCarImageHeight);
      }
      gcCarImage.src = "Car.jpg"; 
    }
    gcBackgroundImage.src = "Streets.jpg";
  }
)//document.addEventListener('DOMContentLoaded'
//------------------------------
//------------------------------
document.body.addEventListener("keydown", e =>
  {
    let lRenderFlag = false;
    e.preventDefault();
    switch(e.key) {
      case 's':
        glDegrees = 0;
        for (let ii = 0; ii < 360; ii++) {
          glDegrees = ii;
          let lCarCenterX = glCarTopLeftX + gcCarImageWidth / 2;
          let lCarCenterY = glCarTopLeftY + gcCarImageHeight / 2;  
          fRender(lCarCenterX, lCarCenterY, glDegrees);
          fSleep(1000);
        } 
        break;
    }
  }
)//document.body.addEventListener("keydown"
//------------------------------
//------------------------------
function fSleep(num) 
{
  let now = new Date();
  let stop = now.getTime() + num;
  while(true) {
      now = new Date();
      if(now.getTime() > stop) return;
  }
}//fSleep
//------------------------------
//------------------------------
function fRender(lCarCenterX, lCarCenterY, glDegrees) 
{
  gcContext.clearRect(0, 0, gcCanvas.width, gcCanvas.height);
  gcContext.drawImage(gcBackgroundImage, 0, 0, gcBackgroundWidth, glBackgroundHeight);
  let lRadians = Math.PI / 180 * glDegrees;
  gcContext.translate(lCarCenterX, lCarCenterY);
  gcContext.rotate(lRadians);
  gcContext.drawImage(gcCarImage, -gcCarImageWidth / 2, -gcCarImageHeight / 2, gcCarImageWidth, gcCarImageHeight);
  gcContext.rotate(-lRadians);
  gcContext.translate(-lCarCenterX, -lCarCenterY);    
}//fRender
//------------------------------

This code is to have a car rotate on a background image. Entering “s” should start the rotation sequence.

To slow the rotation, a fSleep" function has been added (delays 1 sec):

When running code, the car image never changes. Putting a Breakpoint on the fSleep function does display the car rotating with every F5.


The car and background images — Car.jpg => https://snipboard.io/ptkWjJ.jpg — Streets.jpg => https://snipboard.io/4YgDaf.jpg.

The images combined — https://snipboard.io/C8LZXu.jpg

The browser’s rendering thread will not update the page while you have JavaScript code running. A busy loop like your fSleep function (which keeps the CPU usage high) means that your JavaScript code never ends, so the browser won’t render and updates made by the script.

You need to modify your code so it doesn’t render in a simple loop, but does so by repeatedly calling a function using some callback mechanism provided by the browser. The traditional ways of triggering these callbacks are using either setInterval or setTimeout. For example:

let rotate = () => {
    let lCarCenterX = glCarTopLeftX + gcCarImageWidth / 2;
    let lCarCenterY = glCarTopLeftY + gcCarImageHeight / 2;
    fRender(lCarCenterX, lCarCenterY, glDegrees);
    if (glDegrees < 360){
        glDegrees += 1;
        setTimeout(rotate, 2000/360);
    }
};
glDegrees = 0;
rotate();

For animations, the browsers provide a special way of handling this requirement, requestAnimationFrame. This will execute your callback function as soon as the browser is able, and provides a monotomic clock that you can use to time your animation and calculate progress. This allows one to create smoother and consistent animations. For example:

let animationStart, glDegrees = 0;
const animationDuration = 2000; //MS
let rotate = (time) => {
    if (animationStart === undefined) {
        animationStart = time;
    } else {
        glDegrees = (360 / animationDuration) * (time - animationStart);
        glDegrees = Math.min(glDegrees, 360);
    }
    let lCarCenterX = glCarTopLeftX + gcCarImageWidth / 2;
    let lCarCenterY = glCarTopLeftY + gcCarImageHeight / 2;
    fRender(lCarCenterX, lCarCenterY, glDegrees);
    if (glDegrees < 360) {
        window.requestAnimationFrame(rotate);
    }
};
window.requestAnimationFrame(rotate);
3 Likes

kicken, Very interesting! I implemented the first section of code → it works!

I’m new to JavaScript … unlike any other code I’ve ever written. It’s fascinating how setTimeout works.

I’m curious about the setTimeout value of 2000 / 360 which equals 5.555. The reason to select 5.555 msec?

BTW I tried setting that value to 0.00001 → the car spins at the same speed.

The script rotates the car 1 degree at a time, and should complete the full 360 degree rotation in 2000ms, that means you need to update the rotation every 2000ms/360 = ~5.555ms.

I am pretty sure the delay is an integer value, so something like that would effectively be 0. Likewise, the 5.555 ms delay calculated in the example would just be 5ms.

The delay value is also subject to minimums set by the browser, and may be inaccurate due to system load or other reasons. The value you give is more of a suggested delay than a guaranteed delay. MDN documents various reasons why the actual delay may vary from the requested delay. This inaccuracy of the delay is one of the reasons requestAnimationFrame and it’s monotonic clock is preferred for animation effects. For example, if you use both methods side-by-side and reduce the animation time to 1000ms, you can see how the setTimeout method falls behind due to the browser minimums.

2 Likes

Yep. I enter a larger number → it rotates more slowly.

Your code example is exceptional! It provides insight I had not considered!