www139 www139 - 1 month ago 10
Javascript Question

Canvas animation, issue with Math calculations in my javascript code

The idea:

Hi, this is my first experiment with canvas animation. I'm not sure if this concept already exists but I've dubbed my version "two-state animation". The idea is this: A map is created with three sub-maps for each object on-screen. One of the sub-map holds the current state properties (of an animating object), the second holds the final destination (of an animating object), and the third for divided increments to ensure the object (on-screen) reaches its final destination in the number of frames set in `framesForChange.

The problem:

Notice, as the animation progresses, the objects are gradually pushed down to the bottom right but the numbers for x and y axis continue to grow. I'm aware that I'm generating random number to confine it within the width (x) and height (y) of the viewport. Why is this happening? How can I fix it?

The code:

If you have any suggestions for how I could make this more efficient (or, more primarily, fix the problem [mentioned above]), please mention it in your answer or in the comments. Thank you.



window.addEventListener('load', initializeCanvasTwo());

function initializeCanvasTwo() {
var canvas, c, map = {},
framesForChange = 360;

initializeCanvas();
resizeCanvas();
initializeMapping();
draw();

window.addEventListener('resize', function() {
resizeCanvas();
});

function initializeCanvas() {
canvas = document.getElementById('canvasTwo');
c = canvas.getContext('2d');
}

function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
c.width = window.innerWidth;
c.height = window.innerHeight;
}

function initializeMapping() {
var n = Math.floor(window.innerWidth * window.innerHeight / 20000);
for (var i = 0; i < n; i++) {
map[i] = {
current: {
startX: getRandomX(),
startY: getRandomY(),
endX: getRandomX(),
endY: getRandomY()
},
final: {
startX: getRandomX(),
startY: getRandomY(),
endX: getRandomX(),
endY: getRandomY()
},
increment: {

}
};
map[i].increment.startXIncrement = (map[i].final.startX - map[i].current.startX) / framesForChange;
map[i].increment.startYIncrement = (map[i].final.startY - map[i].current.startY) / framesForChange;
map[i].increment.endXIncrement = (map[i].final.endX - map[i].current.endX) / framesForChange;
map[i].increment.endYIncrement = (map[i].final.endY - map[i].current.endY) / framesForChange;
}
}

function getRandomX() {
return Math.floor(Math.random() * window.innerWidth);
}

function getRandomY() {
return Math.floor(Math.random() * window.innerHeight);
}

function updateMap() {
var n = Object.keys(map).length;
for (var i = 0; i < n; i++) {
if (map[i].final.startX - map[i].current.startX < map[i].increment.startXIncrement) {
//startX transition complete: set new destination...
map[i].final.startX = getRandomX();
map[i].increment.startXIncrement = (map[i].final.startX - map[i].current.startX) / framesForChange;
} else {
map[i].current.startX += map[i].increment.startXIncrement;
}

if (map[i].final.startY - map[i].current.startY < map[i].increment.startYIncrement) {
//startY transition complete: set new destination...
map[i].final.startY = getRandomY();
map[i].increment.startYIncrement = (map[i].final.startY - map[i].current.startY) / framesForChange;
} else {
map[i].current.startY += map[i].increment.startYIncrement;
}

if (map[i].final.endX - map[i].current.endX < map[i].increment.endXIncrement) {
//endX transition complete: set new destination...
map[i].final.endX = getRandomX();
map[i].increment.endXIncrement = (map[i].final.endX - map[i].current.endX) / framesForChange;
} else {
map[i].current.endX += map[i].increment.endXIncrement;
}

if (map[i].final.endY - map[i].current.endY < map[i].increment.endYIncrement) {
//endY transition complete: set new destination...
map[i].final.endY = getRandomY();
map[i].increment.endYIncrement = (map[i].final.endY - map[i].current.endY) / framesForChange;
} else {
map[i].current.endY += map[i].increment.endYIncrement;
}
}
}

function draw() {
c.clearRect(0, 0, window.innerWidth, window.innerHeight);
c.strokeStyle = 'blue';
var n = Object.keys(map).length;
for (var i = 0; i < n; i++) {
c.beginPath();
c.moveTo(map[i].current.startX, map[i].current.startY);
c.lineTo(map[i].current.endX, map[i].current.endY);
c.stroke();
c.closePath();
}
updateMap();
requestAnimationFrame(draw);
}
}

html,
body {
margin: 0;
padding: 0;
}
canvas {
width: 100%;
height: 100%;
margin-bottom: -4px;
}

<canvas id="canvasTwo"></canvas>




Answer

The major problem is with the if conditions.

You check if the final - current is less than the increment. But when the x tries to move leftwards or the y upwards (which means start > final) then you create new values for them. So it is only allowed to go to the right and bottom. (to solve this you should use Math.abs on both sides of the if, but that would degrade performance even further)

As slight improvements you should use an array for the map and since all elements take the same frames to complete their animation you should not check if each point has reached its destination, but count the frames and update the end positions all at once.

window.addEventListener('load', initializeCanvasTwo());

function initializeCanvasTwo() {
  var canvas, c, map = [],
    framesForChange = 360,
    currentFrame = 360;

  initializeCanvas();
  resizeCanvas();
  initializeMapping();
  draw();

  window.addEventListener('resize', function() {
    resizeCanvas();
  });

  function initializeCanvas() {
    canvas = document.getElementById('canvasTwo');
    c = canvas.getContext('2d');
  }

  function resizeCanvas() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    c.width = window.innerWidth;
    c.height = window.innerHeight;
  }

  function initializeMapping() {
    var n = Math.floor(window.innerWidth * window.innerHeight / 20000);
    for (var i = 0; i < n; i++) {
      map[i] = {
        current: {
          startX: getRandomX(),
          startY: getRandomY(),
          endX: getRandomX(),
          endY: getRandomY()
        },
        final: {
          startX: getRandomX(),
          startY: getRandomY(),
          endX: getRandomX(),
          endY: getRandomY()
        },
        increment: {

        }
      };
      map[i].increment.startXIncrement = (map[i].final.startX - map[i].current.startX) / framesForChange;
      map[i].increment.startYIncrement = (map[i].final.startY - map[i].current.startY) / framesForChange;
      map[i].increment.endXIncrement = (map[i].final.endX - map[i].current.endX) / framesForChange;
      map[i].increment.endYIncrement = (map[i].final.endY - map[i].current.endY) / framesForChange;
    }
  }

  function getRandomX() {
    return Math.floor(Math.random() * window.innerWidth);
  }

  function getRandomY() {
    return Math.floor(Math.random() * window.innerHeight);
  }

  function updateMap() {
    var n = map.length;
    currentFrame = --currentFrame || framesForChange;
    if (currentFrame == framesForChange){
      // create new values
      for (var i = 0; i < n; i++) {
        map[i].final.startX = getRandomX();
        map[i].final.startY = getRandomY();
        map[i].final.endX = getRandomX();
        map[i].final.endY = getRandomY();
        
        map[i].increment.startXIncrement = (map[i].final.startX - map[i].current.startX) / framesForChange;
        map[i].increment.startYIncrement = (map[i].final.startY - map[i].current.startY) / framesForChange;
        map[i].increment.endXIncrement = (map[i].final.endX - map[i].current.endX) / framesForChange;
        map[i].increment.endYIncrement = (map[i].final.endY - map[i].current.endY) / framesForChange;
      }
    }
    for (var i = 0; i < n; i++) {
        map[i].current.startX += map[i].increment.startXIncrement;
        map[i].current.startY += map[i].increment.startYIncrement;
        map[i].current.endX += map[i].increment.endXIncrement;
        map[i].current.endY += map[i].increment.endYIncrement;
    }
  }

  function draw() {
    c.clearRect(0, 0, window.innerWidth, window.innerHeight);
    
    var n = map.length;
    c.strokeStyle = 'blue';
    for (var i = 0; i < n; i++) {
      c.beginPath();
      c.moveTo(map[i].current.startX, map[i].current.startY);
      c.lineTo(map[i].current.endX, map[i].current.endY);
      c.stroke();
      c.closePath();
    }
    updateMap();
    requestAnimationFrame(draw);
  }
}
html,
body {
  margin: 0;
  padding: 0;
}
canvas {
  width: 100%;
  height: 100%;
  margin-bottom: -4px;
}
<canvas id="canvasTwo"></canvas>

Comments