如何解决在不同浏览器上使用requestAnimationFrame时出现的不同FPS问题?

11
如何解决在不同浏览器中使用`requestAnimationFrame`时出现的不同帧率问题?我正在使用`THREE.js`开发一个3D游戏,其中使用了`requestAnimationFrame`函数,在Google Chrome 15上非常快。但是在Firefox 6上很慢,在IE9上更慢(比Firefox还要慢)。这是一个非常的问题,我想知道是否有解决方案。谢谢。

1
解决这个问题的方法是让您在回调函数中运行的任何代码都变得更快。如何实现这一点,没有看到代码是无法说的... - Boris Zbarsky
5个回答

16

通常做法是创建一个deltaTime(dt)变量,然后将其用作每个动画/更新周期的参数。

这段代码仅用于可视化问题/解决方案。

// ...
timer: function(){
    var now = new Date().getTime(); // get current time
    this.controls.dt = now - this.controls.time; // calculate time since last call
    this.controls.time = now; // update the current application time
    this.controls.frame++; // also we have a new frame
    return this.controls.dt ;
}

对于调用渲染函数的任何情况,您都需要传递dt参数。

// we call the update function with every request frame
update: function(){
    var dt = this.timer();
    _.each(this.activeViews, function(item){ item.update(dt); });  // this is underscore.js syntax
}

item.update(dt) 看起来像这样

//...
var x = this.position.get(x);
x = x + (10*dt); // meaning: x increases 10 units every ms.
this.position.x = x;

5
据我所知,除了使代码的资源消耗更少之外,没有真正解决这个问题的方法。
Chrome似乎是最快的浏览器,但通常情况下,Firefox也不会落后太多,但IE仍然很慢。根据渲染方法,canvas、svg或webGL,它还非常依赖于本地硬件,因为大多数事情都使用客户端来完成,复杂的webGL渲染需要强大的GPU才能实现良好的帧率。
有一些方法可以实时测量帧速率,并相应地更改动画。
以下是一个非常简单的示例,可用于测量帧速率。

function step(timestamp) {
    var time2 = new Date;
    var fps   = 1000 / (time2 - time);
    time = time2;
 
    document.getElementById('test').innerHTML = fps;
    window.requestAnimationFrame(step);
}

var time = new Date(), i = 0;
window.requestAnimationFrame(step);
<div id="test"></div>

这只是一种瞬时的措施,不是很准确,您可能需要一些测量时间来获得更准确的浏览器帧速率。

另外请注意timestamp参数,在requestAnimationFrame中是高分辨率时间戳,最小精度为1毫秒,也可用于确定动画速度和任何浏览器延迟。


2
IE9+实际上非常快。在我的经验中,有时比FF更快。 - kangax

3
Crafty框架做了一些不同寻常的事情,但对于某些情况可能有效--每个绘制周期的游戏刻度数不是恒定的。相反,它会注意到帧速率落后于某个理想目标时,并在执行绘制步骤之前循环多个游戏刻度。您可以在github上查看step function
只要游戏能够平稳运行,这种方法就很有效。但如果您尝试更加处理器密集型的操作,则可能会加剧情况,因为它会优先处理游戏逻辑而非动画。
无论如何,只有当游戏逻辑和渲染逻辑有些分离时才能起作用。(如果它们完全分离,您可能可以将它们放在完全独立的循环中。)

3

在某些浏览器中,requestAnimationFrame的工作方式类似于

setTimeout(callback, 1000 / (16 + N)

其中N是您的代码执行所需的时间。这意味着它将您的FPS限制在62Hz,但如果您的代码运行缓慢,则会将其限制在更低的值。它基本上尝试在每个间隔之间产生16ms的间隔。当然,这对于所有浏览器来说都不是真实的,并且将来可能会发生变化,但它仍然可以让您了解它的工作原理。

即使在每个浏览器中都实现相同,也有许多因素会影响您的代码性能等。您永远无法确定您的代码将以恒定的频率运行。


0
adeneo所提到的,requestAnimationFrame回调函数会传递一个时间戳参数。以下是一种使用该时间戳参数来测量requestAnimationFrame事件之间差异的解决方案,而不是使用Date()函数创建一个单独的变量(performance.now()可能是更好的解决方案)。
此解决方案还包括一个启动/停止选项,以展示为什么我要使用一个单独的函数来初始化previousTimestamp,并为什么我要设置reqID值。

var reqID, previousTimestamp, output;

const raf = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
    window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;

const caf = window.cancelAnimationFrame || window.mozCancelAnimationFrame;

// This is run first to set the previousTimestamp variable with an initial value, and then call the rafLoop function.
const startStop = () => {
    if ($('#start-stop').prop('checked')) {
        reqID = raf(timestamp => {
            previousTimestamp = timestamp;
            reqID = raf(rafLoop);
        });
    }
    else caf(reqID);
};

const rafLoop = timestamp => {
    animation(timestamp - previousTimestamp);
    previousTimestamp = timestamp;
    reqID = raf(rafLoop);
};

// Create animation function based on delta timestamp between animation frames
const animation = millisesonds => {
    output.html(millisesonds);
};

$(document).ready(() => {
    output = $('#output');
    $('#start-stop').change(startStop);
    $('#start-stop').prop('checked', true).trigger('change');
});
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>requestAnimationFrame</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
    <label for="start-stop">Start/Stop: </label><input class="switch" type="checkbox" id="start-stop"><br>
    <div id="output"></div>
</body>

另外请参考https://codepen.io/sassano/pen/wvgxxMp,这是一个带有动画效果的示例,这段代码就是从那里衍生出来的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接