Here is a code of a very simple slideshow, that should show 4 images in 4 seconds, one image per second. Instead, I get a 4-second delay and then all the images get drawn on top of each other. What am I doing wrong?
<html>
<head>
<script langugage="javascript">
// 4 images
var image0 = new Image();
image0.src = "img/image0.png";
var image1 = new Image();
image1.src = "img/image1.png";
var image0 = new Image();
image2.src = "img/image2.png";
var image3 = new Image();
image3.src = "img/image3.png";
// array of 4 images
images = new Array(image0, image1, image2, image3);
// this is the main function
function draw(){
myCanvas = document.getElementById('myCanvas');
ctx = myCanvas.getContext('2d');
counter=0; // this is the index of the next image to be shown
for (var i=0;i<images.length;i++){
setTimeout(draw_next_image, 1000);
ctx.clearRect(0, 0, myCanvas.width, myCanvas.height)
}
}
// this is the function called after each timeout to draw next image
function draw_next_image(){
ctx.drawImage(images[counter], 0, 0);
counter++;
if (counter>images.length) {counter=0;}
}
window.onload = draw;
</script>
</head>
<body>
<canvas id="myCanvas" width="800" height="600"></canvas>
</body>
</html>
UPDATE: THE ANSWER IS:
In the code above I mistakingly assumed that getTimeout function is synchronous, i.e. I expected, that upon its call the program execution is going to stop, wait 1000 milliseconds, then call the draw_next_image and only then execute ctx.clearRect.
In reality Javascript doesn't work like that. In fact getTimeout is asynchronous, i.e. getTimeout sets a Timeout and returns almost instantly and the code execution continues, so that ctx.clearRect gets called right away and actually prior to draw_next_image. So, by the time Timeout expires and calls draw_next_image, execution of code may reach and arbitrary line of code. In my case all the 4 clearRect are going to be called almost at the same time, long before expiration of Timeouts. Then 1000 milliseconds later, all the 4 timeouts are going to expire almost immediately one after another, and all the 4 images going to be drawn almost at the same time, too, without clearRects, which got executed long before.
The problem is that in your code you are treating asynchronous functions as if they where synchronous.
The main points are here:
image0.src = "img/image0.png";
image1.src = "img/image1.png";
image2.src = "img/image2.png";
image3.src = "img/image3.png";
...
setTimeout(draw_next, delayInMilliseconds);
As these calls just falls through once they are invoked, and your code starts to execute the next step before the result from these are (possibly) ready.
You therefor need to chain your calls based on events, for example:
//image counter as there is no guarantee that the last images loaded
//is the last one to finish
var loaded = 0, numOfImages = 4;
//first part of chain, invoke async load
var image0 = document.createElement('img'); //this will work in new Chrome
var image1 = document.createElement('img'); //instead of new Image
var image2 = document.createElement('img');
var image3 = document.createElement('img');
//common event handler when images has loaded with counter
//to know that all images has loaded
image0.onload = image1.onload =
image2.onload = image3.onload = function(e) {
loaded++;
if (loaded === numOfImages)
draw(); // <-- second part of chain, invoke loop
}
//show if any error occurs
image0.onerror = image1.onerror =
image2.onerror = image3.onerror = function(e) {
console.log(e);
}
//invoke async loading... you can put these four into your
//window.onload if you want to
image0.src = "img/image0.png";
image1.src = "img/image1.png";
image2.src = "img/image2.png";
image3.src = "img/image3.png";
// this is the main function
function draw() {
var images = new Array(image0, image1, image2, image3),
counter = 0,
delayInMilliseconds = 4000,
maxNum = images.length - 1,
myCanvas = document.getElementById('myCanvas'),
ctx = myCanvas.getContext('2d'),
me = this; //this we need for setTimeout()
//third part of chain, have a function to invoke by setTimeout
this._draw = function() {
//if the next image will cover the canvas
//there is no real need to clear the canvas first.
//I'll leave it here as you ask for this specifically
ctx.clearRect(0, 0, myCanvas.width, myCanvas.height)
ctx.drawImage(images[counter++], 0, 0);
if (counter > maxNum) counter = 0;
setTimeout(me._draw, delayInMilliseconds); //use me instead of this
}
this._draw(); //START the loop
}
Working demo here:
http://jsfiddle.net/AbdiasSoftware/dhxNz/
The _draw() is wrapped in draw() to localize the variables and also to make sure that _draw() doesn't end up on the window object. For the same reason we store a reference to this as this is changed to window object when its code is invoked. me (or what you want to call it) makes sure that we are calling on the right object so that we have access to the local variables (canvas, ctx, counter etc.).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With