【问题标题】:How to stop a requestAnimationFrame recursion/loop?如何停止 requestAnimationFrame 递归/循环?
【发布时间】:2012-05-30 22:40:10
【问题描述】:

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

【问题讨论】:

    标签: javascript html fullscreen webgl three.js


    【解决方案1】:

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

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

    工作示例:

    const timeElem = document.querySelector("#time");
    var requestId;
    
    function loop(time) {
        requestId = undefined;
        
        doStuff(time)
        start();
    }
    
    function start() {
        if (!requestId) {
           requestId = window.requestAnimationFrame(loop);
        }
    }
    
    function stop() {
        if (requestId) {
           window.cancelAnimationFrame(requestId);
           requestId = undefined;
        }
    }
    
    function doStuff(time) {
      timeElem.textContent = (time * 0.001).toFixed(2);
    }
      
    
    document.querySelector("#start").addEventListener('click', function() {
      start();
    });
    
    document.querySelector("#stop").addEventListener('click', function() {
      stop();
    });
    <button id="start">start</button>
    <button id="stop">stop</button>
    <div id="time"></div>

    【讨论】:

    • 应该是cancelAnimationFrame 而不是cancelRequestAnimationFrame
    • 改变了,虽然在我的辩护中它最初被称为cancelRequestAnimationFrame。您可以通过在 Chrome 中打开控制台并输入 webkitCancel 来验证这一点,您会看到 webkitCancelRequestAnimationFrame 在 Chrome 33 中仍然存在
    • 这两个函数的文档都可以在 MDN 上找到:developer.mozilla.org/en-US/docs/Web/API/window/…(感谢您提供的精彩信息!)
    • 我发现,你不能从函数内部停止循环:这里是demo。仅来自外部:-)
    • 其实你可以从函数内部取消循环,像这样:setTimeout(function(){window.cancelAnimationFrame(requestId);},0);
    【解决方案2】:

    停止就像不再调用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
    

    【讨论】:

    • 是的。这实际上是最简单的方法。
    • 但这并不高效,因为您必须以这种方式在每个动画帧内执行检查。我发现 cancelAnimationFrame() 在繁重的动画循环中执行效率更高。
    • 我不明白为什么 user3363398 的这个解决方案比 gman 的效率低。在 gmans 解决方案中,循环内有一个函数调用,然后该函数调用依次执行检查。不是更重吗?一项检查与函数调用+检查。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

        if(!lives) {
            alert("GAME OVER");
            return;
        }
        
        // looping the draw()
        requestAnimationFrame(draw);
        

        【讨论】:

        • 这属于@user3363398 提出的停止动画帧循环的“只是不启动下一个循环”方法,替代方法是使用cancelAnimationFrame()
        【解决方案6】:
        var myAnim //your requestId
        function anim()
               {
               //bla bla bla
               //it's important to update the requestId each time you're calling reuestAnimationFrame
               myAnim=requestAnimationFrame(anim)
               }
        

        让我们开始吧

        myAnim=requestAnimationFrame(anim)
        

        让我们停下来

        //the cancelation uses the last requestId
        cancelAnimationFrame(myAnim)
        

        Reference

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-01-06
          • 1970-01-01
          • 2012-06-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-05-18
          相关资源
          最近更新 更多