Simplest slideshow in HTML5 canvas, canvas.context.clearRect not working with setTimeout(HTML5 画布中最简单的幻灯片,canvas.context.clearRect 不适用于 setTimeout)
问题描述
这是一个非常简单的幻灯片的代码,它应该在 4 秒内显示 4 张图像,每秒一张图像.相反,我得到了 4 秒的延迟,然后所有图像都被绘制在彼此之上.我做错了什么?
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>
更新:答案是:
在上面的代码中,我错误地假设 getTimeout
函数是同步的,即我预期,在调用它时程序执行将停止,等待 1000 毫秒,然后调用 draw_next_image
然后才执行 ctx.clearRect
.
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
.
实际上 Javascript 并不是这样工作的.实际上 getTimeout
是异步的,即 getTimeout
设置一个 Timeout 并几乎立即返回并且代码继续执行,因此 ctx.clearRect
被正确调用离开,实际上是 在 draw_next_image
之前.因此,当 Timeout 到期并调用 draw_next_image
时,代码的执行可能会到达任意行代码.在我的情况下,所有 4 个 clearRect
将几乎同时被调用,远在超时到期之前.然后 1000 毫秒后,所有 4 个超时将几乎一个接一个地立即到期,并且所有 4 个图像也将几乎同时绘制,没有 clearRects
,它执行了很长时间之前.
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.
要点在这里:
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
}
这里的工作演示:
http://jsfiddle.net/AbdiasSoftware/dhxNz/
_draw()
被包裹在 draw()
中以本地化变量并确保 _draw()
不会结束在 window
对象上.出于同样的原因,我们存储对 this
的引用,因为当调用其代码时,this
将更改为 window
对象.me
(或你想调用的)确保我们调用的是正确的对象,以便我们可以访问局部变量(canvas、ctx、counter 等).
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.).
这篇关于HTML5 画布中最简单的幻灯片,canvas.context.clearRect 不适用于 setTimeout的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!
本文标题为:HTML5 画布中最简单的幻灯片,canvas.context.clearRect 不适用于 setTimeout
基础教程推荐
- Chart.js 在线性图表上拖动点 2022-01-01
- 用于 Twitter 小部件宽度的 HTML/CSS 2022-01-01
- html表格如何通过更改悬停边框来突出显示列? 2022-01-01
- 自定义 XMLHttpRequest.prototype.open 2022-01-01
- 如何使用TypeScrip将固定承诺数组中的项设置为可选 2022-01-01
- 直接将值设置为滑块 2022-01-01
- Electron 将 Node.js 和 Chromium 上下文结合起来意味着 2022-01-01
- Vue 3 – <过渡>渲染不能动画的非元素根节点 2022-01-01
- 我可以在浏览器中与Babel一起使用ES模块,而不捆绑我的代码吗? 2022-01-01
- 如何使用JIT在顺风css中使用布局变体? 2022-01-01