如何停止 requestAnimationFrame 递归/循环?

IT技术 javascript html fullscreen webgl three.js
2021-02-24 12:21:38

我正在使用 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方法被注释掉。

6个回答

一种启动/停止方式是这样的

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

工作示例:

这应该是cancelAnimationFrame而不是cancelRequestAnimationFrame
2021-04-21 12:21:38
这两个函数的文档都可以在 MDN 上找到:developer.mozilla.org/en-US/docs/Web/API/window/...(感谢提供很棒的信息!)
2021-04-27 12:21:38
改变了,虽然在我的辩护中它最初被称为cancelRequestAnimationFrame. 您可以通过在 Chrome 中打开控制台并输入来验证这一点webkitCancel,您将webkitCancelRequestAnimationFrame在 Chrome 33 中看到仍然存在
2021-04-30 12:21:38
实际上,您可以从函数内部取消循环,如下所示: setTimeout(function(){window.cancelAnimationFrame(requestId);},0);
2021-05-13 12:21:38
在我发现的事情上,你不能从函数内部停止循环:这是一个演示只能从外面:-)
2021-05-15 12:21:38

停止就像不再调用 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
我不明白为什么 user3363398 的这个解决方案比 gman 的效率低。在 gmans 解决方案中,循环内部有一个函数调用,该函数调用依次执行检查。那不是更重吗?一次检查与函数调用 + 检查。
2021-04-25 12:21:38
是的。这其实是最简单的方法。
2021-05-02 12:21:38
但这效率不高,因为您必须以这种方式在每个动画帧内执行检查。我发现 cancelAnimationFrame() 在繁重的动画循环中执行效率更高。
2021-05-12 12:21:38

我建议查看requestAnimationFrame polyfill gibhub 页面。有关于如何实施的讨论。

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

我玩了一个2D Breakout Game的教程,他们也使用了 requestAnimationFrame ,我用一个简单的return停止了它如果省略 return 的值,则 return 语句结束函数执行。

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

// looping the draw()
requestAnimationFrame(draw);
这将属于@user3363398 提出的停止动画帧循环的“只是不启动下一个循环”的方法,另一种方法是使用cancelAnimationFrame().
2021-05-01 12:21:38