如何停止requestAnimationFrame递归/循环?


68

我将Three.js与WebGL渲染器一起使用,以制作一个在play单击链接时全屏显示的游戏。对于动画,我使用requestAnimationFrame

我这样启动它:

self.animate = function()
{
    self.camera.lookAt(self.scene.position);

    self.renderer.render(self.scene, self.camera);

    if (self.willAnimate)
        window.requestAnimationFrame(self.animate, self.renderer.domElement);
}

self.startAnimating = function()
{
    self.willAnimate = true;
    self.animate();
}

self.stopAnimating = function()
{
    self.willAnimate = false;
}

在需要时,我调用该startAnimating方法,是的,它确实按预期工作。但是,当我调用该stopAnimating函数时,事情就坏了!不过,没有报告的错误...

设置基本上是这样的:

  • play页面上有一个链接
  • 用户单击链接后,渲染器domElement应全屏显示,并且
  • startAnimating调用方法,渲染器开始渲染东西
  • 单击转义后,我注册一个fullscreenchange事件并执行stopAnimating方法
  • 该页面尝试退出全屏显示,但确实如此,但是整个文档完全空白

我很确定我的其他代码还可以,并且我以某种requestAnimationFrame错误的方式停止了。我的解释可能很烂,所以我将代码上传到了我的网站,您可以在这里看到它的发生:http : //banehq.com/Placeholdername/main.html

这是我不尝试调用动画方法的版本,并且全屏输入和输出均可使用:http : //banehq.com/Correct/Placeholdername/main.html

第一次play单击后,游戏将初始化并start执行其方法。全屏退出后,将stop执行游戏的方法。每隔一次play单击一次,游戏将仅执行其start方法,因为无需再次对其进行初始化。

外观如下:

var playLinkHasBeenClicked = function()
{
    if (!started)
    {
        started = true;

        game = new Game(container); //"container" is an empty div
    }

    game.start();
}

这里是如何的startstop方法如下所示:

self.start = function()
{
    self.container.appendChild(game.renderer.domElement); //Add the renderer's domElement to an empty div
    THREEx.FullScreen.request(self.container);  //Request fullscreen on the div
    self.renderer.setSize(screen.width, screen.height); //Adjust screensize

    self.startAnimating();
}

self.stop = function()
{
    self.container.removeChild(game.renderer.domElement); //Remove the renderer from the div
    self.renderer.setSize(0, 0); //I guess this isn't needed, but welp

    self.stopAnimating();
}

这和工作版本之间的唯一区别是,startAnimatingstopAnimating方法调用startstop方法被注释掉。

Answers:


122

一种开始/停止的方式是这样的

var requestId;

function loop(time) {
    requestId = undefined;

    ...
    // do stuff
    ...

    start();
}

function start() {
    if (!requestId) {
       requestId = window.requestAnimationFrame(loop);
    }
}

function stop() {
    if (requestId) {
       window.cancelAnimationFrame(requestId);
       requestId = undefined;
    }
}

工作示例:


7
应该cancelAnimationFrame代替cancelRequestAnimationFrame吗?
ech

7
改变了,尽管在我的辩护中它最初被称为cancelRequestAnimationFrame。您可以通过在Chrome中打开控制台并输入内容来验证这一点,webkitCancelwebkitCancelRequestAnimationFrame
直到

可以在MDN上找到这两个功能的文档,网址为:developer.mozilla.org/en-US/docs/Web/API/window / ...(感谢您提供的出色信息!)
Nigel B. Peck

1
在我发现的事情上,您无法从函数内部停止循环:这是一个演示。仅从外部:-)
传说

2
实际上,您可以从函数内部取消循环,如下所示:setTimeout(function(){window.cancelAnimationFrame(requestId);},0);
Legends

20

停止就像不再调用requestAnimationFrame一样简单,而重新启动就是再次调用它。例如)

        var pause = false;
        function loop(){
                //... your stuff;
                if(pause) return;
                window.requestionAnimationFrame(loop);
        }
       loop(); //to start it off
       pause = true; //to stop it
       loop(); //to restart it

是。这实际上是最简单的方法。
Roko C. Buljan '16

4
但这效率不高,因为您必须以这种方式在每个动画帧内执行一次检查。我发现cancelAnimationFrame()在繁重的动画循环上执行效率更高。
谢尔盖·卢金


2

因此,在进行了更多测试之后,我发现确实是我的其他代码引起了问题,而不是动画停止了(毕竟这只是一个简单的递归)。问题在于从页面中动态添加和删除渲染器的domElement。在我停止这样做之后,因为确实没有理由这样做,并且在进行初始化的地方将其包含在内,一切就可以正常工作了。


2

我玩过2D Breakout游戏的教程,在该教程中,他们还使用了requestAnimationFrame,并通过简单的返回将其停止。如果省略return的值,则return语句结束函数执行。

if(!lives) {
    alert("GAME OVER");
    return;
}

// looping the draw()
requestAnimationFrame(draw);

这将属于“只是不启动下一个周期”,以停止@ user3363398提出的动画帧循环,替代方法是使用cancelAnimationFrame()
Magnus Lind Oxlund

0

您实际上并不需要窗口引用,只需直接进行操作即可:

var myLoopHandler
function myLoop()
       {
       console.log('running myLoop...')
       myLoopHandler=requestAnimationFrame(myLoop)
       }

让我们开始吧:

myLoop()

让我们停止它:

cancelAnimationFrame(myLoopHandler)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.