如何停止 requestAnimationFrame 递归/循环?

我使用 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方法,渲染器开始渲染内容
  • 单击 escape 之后,注册一个 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方法中被注释掉了。

90194 次浏览

I would suggest having a look at the requestAnimationFrame polyfill gibhub page. There are discussions about how this is implemented.

So, after doing some more testing, I've found out that it was, indeed, my other code that posed a problem, not the animation stopping (it was a simple recursion after all). The problem was in dynamically adding and removing the renderer's domElement from the page. After I've stopped doing that, for there was really no reason to do so, and included it once where the initialization was happening, everything started working fine.

One way to start/stop is like this

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

Working example:

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>

Stopping is as simple as not calling requestAnimationFrame anymore, and restarting is to call it it again. ex)

        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

I played around with the tutorial of a 2D Breakout Game where they also used requestAnimationFrame and I stopped it with a simple return. The return statement ends function execution if the value of return is omitted.

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


// looping the draw()
requestAnimationFrame(draw);
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)
}

Let's start it

myAnim=requestAnimationFrame(anim)

Let's stop it

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

Reference