javascript - HTML5 Canvas 中最简单的幻灯片,canvas.context.clearRect 不适用于 setTimeout

标签 javascript html canvas html5-canvas

这是一个非常简单的幻灯片代码,应该在 4 秒内显示 4 张图片,每秒一张。相反,我得到了 4 秒的延迟,然后所有图像都绘制在彼此之上。我做错了什么?

<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>

更新:答案是:

在上面的代码中,我错误地假设 getTimeout 函数是同步的,即我预计,在调用它时程序执行将停止,等待 1000 毫秒,然后调用 draw_next_image 然后才执行 ctx.clearRect

实际上 Javascript 并不是那样工作的。事实上 getTimeout 是异步的,即 getTimeout 设置一个超时并几乎立即返回并且代码执行继续,因此 ctx.clearRect 被正确调用离开并且实际上之前 draw_next_image。因此,当 Timeout 到期并调用 draw_next_image 时,代码的执行可能会到达任意代码行。在我的例子中,所有 4 个 clearRect 几乎同时被调用,远在超时到期之前。然后 1000 毫秒后,所有 4 个超时几乎立即依次到期,所有 4 个图像也将几乎同时绘制,没有 clearRects,它执行了很长时间之前。

最佳答案

问题在于,在您的代码中,您将异步函数视为同步函数。

要点在这里:

image0.src = "img/image0.png";
image1.src = "img/image1.png";
image2.src = "img/image2.png";
image3.src = "img/image3.png";
...
setTimeout(draw_next, delayInMilliseconds);

因为这些调用一旦被调用就会失败,并且您的代码在这些调用的结果(可能)准备好之前开始执行下一步。

因此,您需要根据事件链接您的调用,例如:

//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
}

此处的工作演示:
http://jsfiddle.net/AbdiasSoftware/dhxNz/

_draw() 被包装在 draw() 中以本地化变量并确保 _draw() 不会结束于 window 对象。出于同样的原因,我们存储对 this 的引用,因为 this 在调用其代码时更改为 window 对象。 me(或者你想怎么调用它)确保我们正在调用正确的对象,以便我们可以访问局部变量(canvas、ctx、counter 等)。

关于javascript - HTML5 Canvas 中最简单的幻灯片,canvas.context.clearRect 不适用于 setTimeout,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16931072/

相关文章:

html - 使用 flexbox 如何在 div 下居中文本

html - 如何添加一个宽度为 100% 且位置为 :fixed 的按钮

PHP 联系我们表单在页面重新加载时发送

html - 自动定位和缩放嵌入 iFrame

javascript - 如何更改已经显示的 Bootstrap 弹出窗口的内容?

javascript - 将 View 的触摸位置转换为 WebView 的 HTML 屏幕位置

javascript - 使用 jquery.calculation.js 和动态添加的表单字段

javascript - 即时下载 Canvas 图像

javascript - 为什么 javascript 中的 html2canvas 不支持谷歌地图和图表?

javascript - 在 child 之间切换时,焦点和模糊如何使 DOM 冒泡?