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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16931072/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 06:30:21  来源:igfitidea点击:

Simplest slideshow in HTML5 canvas, canvas.context.clearRect not working with setTimeout

javascripthtmlcanvashtml5-canvas

提问by Boris Burkov

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?

这是一个非常简单的幻灯片的代码,它应该在 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>

UPDATE: THE ANSWER IS:

更新:答案是:

In the code above I mistakingly assumed that getTimeoutfunction 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_imageand only then execute ctx.clearRect.

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

In reality Javascript doesn't work like that. In fact getTimeoutis asynchronous, i.e. getTimeoutsets a Timeout and returns almost instantly and the code execution continues, so that ctx.clearRectgets called right away and actually priorto 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 clearRectare 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.

实际上,Javascript 不是那样工作的。事实上getTimeout是异步的,即getTimeout设置一个超时和回报几乎立即和代码将继续执行,从而使ctx.clearRect被调用的时候了,实际上之前draw_next_image。因此,当 Timeout 到期并调用 时draw_next_image,代码的执行可能会到达任意代码行。在我的情况下,所有 4 个clearRect都将几乎同时被调用,远在超时到期之前。然后 1000 毫秒后,所有 4 个超时将几乎立即一个接一个到期,并且所有 4 个图像也将几乎同时绘制,而没有clearRects,它很早就执行了。

回答by

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

这里的工作演示: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 windowobject. For the same reason we store a reference to thisas thisis changed to windowobject 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.).

_draw()包装在draw()本地化的变量,也确保_draw()不会在对最终window对象。出于同样的原因,我们存储对thisas的引用,在调用其代码时this更改为windowobject。me(或者你想叫它什么)确保我们正在调用正确的对象,以便我们可以访问局部变量(画布、ctx、计数器等)。

回答by Half Crazed

You have a few issues with that, including naming your image variables wrong.

您对此有一些问题,包括错误地命名您的图像变量。

Try this:

试试这个:

// 4 images
var image0 = new Image();
image0.src = "http://placekitten.com/200/300";
var image1 = new Image();
image1.src = "http://placekitten.com/205/305";
var image2 = new Image();
image2.src = "http://placekitten.com/300/400";
var image3 = new Image();
image3.src = "http://placekitten.com/800/600";
// array of 4 images
images = new Array(image0, image1, image2, image3);

// global counter and canvas
var counter = 0, ctx, interval;

// this is the main function
function draw(){
    myCanvas = document.getElementById('myCanvas');
    ctx = myCanvas.getContext('2d');
    interval = setInterval(draw_next_image, 1000);
}

// this is the function called after each timeout to draw next image
function draw_next_image(){
    ctx.clearRect(0, 0, myCanvas.width, myCanvas.height);
    ctx.drawImage(images[counter], 0, 0);
    counter++;
    if (counter==images.length) {counter=0;}
}

window.onload = draw;

See: http://jsfiddle.net/8c9MM/1/for example. You can also pause the slideshow since we're assigning interval to the setInterval()function

例如,参见:http: //jsfiddle.net/8c9MM/1/。您还可以暂停幻灯片,因为我们正在为setInterval()函数分配间隔

回答by An_tho_ny

How about this.

这个怎么样。

HTML

HTML

<!DOCTYPE HTML>
<html>
    <head>
        <title>Slider</title>
        <meta charset="utf-8">
        <link type="text/css" rel="stylesheet" href="css/main.css">
    </head>
    <body>
        <canvas id="canvas" width="600" height="400"></canvas>
        <script type="text/javascript" src="js/slider.js"></script>
    </body>
</html>

Javascript

Javascript

// The 3 images
var im1 = new Image();
im1.src = "img/kitten1.jpg";
var im2 = new Image();
im2.src = "img/kitten2.jpg";
var im3 = new Image();
im3.src = "img/kitten3.jpg";

// Starting position of the 3 images
var x1 = 0;
var x2 = -600;
var x3 = -1200;

var counter = 0;
var img_count = 0;

// Canvas
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
//This draws the first image when the page is loaded
ctx.drawImage(im1, x1, 0);

function sliderMove() {
    if(counter <= 590) {
        x1+=10;
        ctx.drawImage(im1,x1,0);
        x2+=10;
        ctx.drawImage(im2,x2,0);
        x3+=10;         
        ctx.drawImage(im3,x3,0);


        counter+=10;
    }
    else {
        counter = 0;
        img_count++

        if(img_count == 1) {
            x1 = -1200;
        }else if(img_count == 2) {
            x2 = -1200;
        }else if(img_count == 3) {
            x3 = -1200;
        }
        // This stops move_loop once counter gets to 600
        clearInterval(move_loop);
    }
}
function moveImg() {
//This part moves all of the images 20px to the right every 10ms
    move_loop = setInterval(sliderMove, 10);
    if(img_count > 2) {
        img_count = 0;
    }
}
// This executes the moveImg function every 5 seconds
image_loop = setInterval(timer, 5000);

Sorry if the codes not very organized. This is the first time that I have created anything with javascript.

对不起,如果代码不是很有条理。这是我第一次用 javascript 创建任何东西。