如何使用 setTimeout 延迟绘制递归函数



我找到了一个递归函数的很好的例子,它在这里生成一个简单的分形树(如下所示)。

var canvas = document.getElementById('canvas_main');
canvas.width  = 600;
canvas.height = 600;
var ctx = canvas.getContext('2d');
function draw(x, y, len, ang){
ctx.save();
ctx.beginPath();
ctx.translate(x, y);
ctx.rotate(ang * Math.PI/180);
ctx.moveTo(0, 0);
ctx.lineTo(0, -len);
ctx.stroke();
if (len < 10) {
ctx.restore();
return;
}
draw(0, -len, len*0.8, -15);
draw(0, -len, len*0.8,  15);
ctx.restore();
}
draw(300, 600, 120, 0);
<canvas id="canvas_main"></canvas>

我想延迟迭代的绘制。我认为 setTimeout() 函数可以帮助我,但我似乎无法正确处理它。

最让我困惑的是为什么简单地包装 draw() 函数不起作用:

setTimeout(function(){
draw(0, -len, len*0.8,  15);
draw(0, -len, len*0.8, -15);
ctx.restore();
},500)

上下文转换给我带来了麻烦。如果第二个绘制功能和恢复功能像这样被禁用,我可以让一侧工作:

setTimeout(function(){
draw(0, -len, len*0.8,  15);
//draw(0, -len, len*0.8, -15);
//ctx.restore();
},500)

但我没有比这更进一步的了。如何分步绘制整棵树? 有人可以解释为什么包装不起作用吗?谢谢!

编辑: 作为一个额外的问题,是否可以使用 requestAnimationFrame() 方法执行此操作?

超时

不会叠加。

用于每次迭代移动线条的方法不跟踪分支的位置,而是依赖于ctx.save()ctx.restore()状态堆栈函数返回正确的位置以移动到下一个分支。

使用设置超时意味着在递归到达最后一个分支之前调用还原。

setTimeout(function(){
draw(0, -len, len*0.8,  15); // this function draws and exits 
draw(0, -len, len*0.8, -15); // this function draws and exits 
ctx.restore(); // the restore is called before the above two functions
// have completed their next iterations as that is waiting
// for the timeouts.
},500)

只有在绘制了上述所有分支后,才能调用还原。

在函数中保留状态。

简单的方法是删除保存和还原并手动执行转换。这样,每个分支的状态都存储在函数中,而不是单独的堆栈。

下面是使用超时的修改。x 和 y 位置现在包含分支起始位置,下一个分支位置在代码中计算。

超时对于每个分支都有一个,并且时间是随机的,这样可以保持渲染的流畅性。

为了好玩,还增加了一条线宽。 :)

var canvas = document.getElementById('canvas_main');
canvas.width  = 600;
canvas.height = 600;
var ctx = canvas.getContext('2d');
function draw(x, y, len, ang, width){
ctx.lineWidth = width;
// draw the branch
ctx.beginPath();
ctx.lineTo(x, y);
// get the end position
x += Math.cos(ang) * len;
y += Math.sin(ang) * len;
ctx.lineTo(x, y);
ctx.stroke();
if (len > 10) {
setTimeout(()=>{                
draw(x , y , len * 0.8, ang - 0.2, width * 0.8);
}, 300 + Math.random() * 100);
setTimeout(()=>{
draw(x , y , len * 0.8, ang + 0.2, width * 0.8);
}, 300 + Math.random() * 100);
}
}
draw(300, 600, 120, -Math.PI /2,4);
<canvas id="canvas_main"></canvas>