How to render HTML5 canvas within a loop

12,782

Solution 1

Your best take is to separate concerns : have an animation loop continuously running, and, "each time [you] get a new information", update your scene description, it will draw as soon as the display is ready.

So a first very simple demo : here the 'new information' is a user click.

var cv = document.getElementById('cv');
var ctx = cv.getContext('2d');
var cvWidth = cv.width;
var cvHeight = cv.height;

// Simple scene description : an array of colored rects
var everyObject = [
  [60, 70, 30, 30, 'yellow']
];

// animation : always running loop.

function animate() {
  // call again next time we can draw
  requestAnimationFrame(animate);
  // clear canvas
  ctx.clearRect(0, 0, cvWidth, cvHeight);
  // draw everything
  everyObject.forEach(function(o) {
    ctx.fillStyle = o[4];
    ctx.fillRect(o[0], o[1], o[2], o[3]);
  });
  // 
  ctx.fillStyle = '#000';
  ctx.fillText('click to add random rects', 10, 10);
}

animate();


// click handler to add random rects
window.addEventListener('click', function() {
  addRandRect();
});

function addRandRect() {
  var randColor = Math.random() > 0.5 ? 'blue' : 'red';
  everyObject.push([Math.random() * cvWidth, Math.random() * cvHeight, 10 + Math.random() * 40, 10 + Math.random() * 40, randColor]);
}
<canvas id='cv' width=400 height=200></canvas>

Now if you want some kind of animation, or a simple version using setTimeOut would be :

var cv = document.getElementById('cv');
var ctx = cv.getContext('2d');
var cvWidth = cv.width;
var cvHeight = cv.height;

// Simple scene description : an array of colored rects
var everyObject = [
  [60, 70, 30, 30, 'yellow']
];

// animation : always running loop.

function animate() {
  // call again next time we can draw
  requestAnimationFrame(animate);
  // clear canvas
  ctx.clearRect(0, 0, cvWidth, cvHeight);
  // draw everything
  everyObject.forEach(function(o) {
    ctx.fillStyle = o[4];
    ctx.fillRect(o[0], o[1], o[2], o[3]);
  });
  // 
  ctx.fillStyle = '#000';
  ctx.fillText('click to add 4 random rects with a delay', 10, 10);
}

animate();


// click handler to add random rects
window.addEventListener('click', function() {
  addRandRect();
  setTimeout(addRandRect, 300);
  setTimeout(addRandRect, 600);
  setTimeout(addRandRect, 900);
});

function addRandRect() {
  var randColor = Math.random() > 0.5 ? 'blue' : 'red';
  everyObject.push([Math.random() * cvWidth, Math.random() * cvHeight, 10 + Math.random() * 40, 10 + Math.random() * 40, randColor]);
}
  <canvas id='cv' width=400 height=200></canvas>

(By the way i wrote about animation here if interested : http://codepen.io/gamealchemist/post/animationcanvas1 )

Solution 2

You could take a look at requestAnimationFrame: https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame

And us it something like this:

function loop() {
    addLines(ga.getBestCreature());

    // execute loop function over and over
    requestAnimationFrame(loop);
}
Share:
12,782
Hasholef
Author by

Hasholef

Updated on August 21, 2022

Comments

  • Hasholef
    Hasholef over 1 year

    I'm trting to do the following:

    I'm running through a for loop and each time I get a new information and want to draw it on the canvas. The problem is that the canvas is being drawn just after the loop finish, and I can't see no animation. If I step in with debug, or with alert messages, it will draw to the canvas immediatly. How can I do it?

    Here is my code, as you can see I've already tried with setTimeout in multiple ways but didn't worked.

    for(var i=0;i<1000; i ++)
        {
            addLines(ga.getBestCreature());   // This method draw on the canvas 
    
            setTimeout(function() {
            ga.startEvolution(1);       // This method change the best creature
            }, 10);   
        }
    

    The drowing method:

    function addLines(best) {
    
        if(!best) {
            return;
        }
        var global_path = best.data;
    
        redraw(); // Clear off the canvas
    
        var cityA;
        var cityB;
        for (var i = 0; i < (global_path.length - 1); i++) {
    
            cityA = ga.cities[global_path[i]];
            cityB = ga.cities[global_path[i+1]];
    
            ctx.moveTo(cityA.x,cityA.y);
            ctx.lineTo(cityB.x,cityB.y);
            ctx.stroke();
        }
    }
    

    Any Ideas??

    Update: After was advised to use the requestAnimationFrame, I've came out with something like this. Is this the best approuch? I'm updating a global variable called 'run' on click events

    var run = 0;
    function animate() {
    
        if(run > 0) {
            ga.startEvolution(1);
            run--;
        }
    
    addLines(ga.getBestCreature());
        requestAnimationFrame(animate);
    }
    
    animate();