使用requestAnimationFrame控制fps?
使用requestAnimationFrame控制fps?
似乎requestAnimationFrame
现在是动画的事实标准方式。在大多数情况下,它对我来说效果还不错,但是现在我想做一些画布动画,我想知道:有没有办法确保它以特定的fps运行?我知道rAF的目的是为了保持动画的平滑,我可能会冒着动画变得不连贯的风险,但现在它似乎以任意不同的速度运行,我想知道是否有办法解决这个问题。
我可以使用setInterval
,但我想要rAF提供的优化(特别是在标签焦点在时自动停止)。
如果有人想查看我的代码,基本上是这样的:
animateFlash: function() { ctx_fg.clearRect(0,0,canvasWidth,canvasHeight); ctx_fg.fillStyle = 'rgba(177,39,116,1)'; ctx_fg.strokeStyle = 'none'; ctx_fg.beginPath(); for(var i in nodes) { nodes[i].drawFlash(); } ctx_fg.fill(); ctx_fg.closePath(); var instance = this; var rafID = requestAnimationFrame(function(){ instance.animateFlash(); }) var unfinishedNodes = nodes.filter(function(elem){ return elem.timer < timerMax; }); if(unfinishedNodes.length === 0) { console.log("done"); cancelAnimationFrame(rafID); instance.animate(); } }
其中Node.drawFlash()
只是根据计数变量确定半径然后绘制一个圆的一些代码。
控制帧率是通过requestAnimationFrame进行的。为了控制帧率,需要在代码中测试自上一帧循环执行以来的经过的时间。只有当指定的帧率间隔经过了,才执行绘制代码。
具体的实现方法如下:
var stop = false; var frameCount = 0; var $results = $("#results"); var fps, fpsInterval, startTime, now, then, elapsed; function startAnimating(fps) { fpsInterval = 1000 / fps; then = Date.now(); startTime = then; animate(); } function animate() { requestAnimationFrame(animate); now = Date.now(); elapsed = now - then; if (elapsed > fpsInterval) { then = now - (elapsed % fpsInterval); // Put your drawing code here } }
这种方法是根据经过的时间来控制帧率的,通过计算上一帧绘制的时间与设定的帧率间隔的差值来决定是否绘制下一帧。
以上就是通过requestAnimationFrame来控制帧率的方法和实现原理。
控制fps(帧速率)的问题一直是开发者关注的一个问题。在这段代码中,使用了setTimeout来包裹requestAnimationFrame函数,以实现控制帧速率的效果。将调用requestAnimationFrame的代码放在setTimeout中,是因为requestAnimationFrame会在下一次重绘之前调用函数,如果使用setTimeout来延迟更新,就会错过这个时间窗口。但反过来做是可行的,因为你只是在等待一段时间才发出请求。
这种方法在保持帧速率的同时,还能避免CPU过热。它简单易行,适用于轻量级的动画。然而,对于一些设备来说,它可能会有一些不同步的问题。在使用这种技术时,当连接到方向传感器时,可能会出现滞后或抖动的问题。后来,我发现使用单独的setInterval,并通过对象属性在传感器、setInterval帧和RAF帧之间进行通信,可以实现传感器和RAF的实时性,而通过setInterval的属性更新来控制动画时间。
某些情况下他的显示器是60FPS,如果他设置fps为60,使用这段代码只能得到大约50FPS。他想要将帧速率减慢到60,因为有些人的显示器是120FPS,但他不想影响其他人。这实际上是一个困难的问题。
导致帧速率低于预期的原因是因为setTimeout可能在延迟时间之后执行回调函数。有很多可能的原因导致这种情况。每次循环都需要花费时间来设置新的定时器和执行一些代码,然后再设置新的延迟。你无法精确控制这个过程,所以应该始终考虑到结果比预期的要慢。但是,由于无法确定延迟的具体时间,试图降低延迟也是不准确的。在浏览器中,JS并不适用于如此精确的操作。
这种方式会导致明显的卡顿,不适用于任何生产游戏。为了更好地控制帧速率,可能需要使用其他方法或技术。
控制fps可以使用requestAnimationFrame来实现,这是因为屏幕的刷新率是固定的,通常为60 FPS。如果我们想要24 FPS,我们在屏幕上将无法真正显示为24 fps,因为显示器只能以15 fps、30 fps或者60 fps(一些显示器甚至可以达到120 fps)的同步帧速率显示。但是,为了计时目的,我们可以计算和更新时间。
我们可以通过将计算和回调封装到一个对象中,来构建控制帧率的所有逻辑。
然后,可以添加一些控制和配置代码,例如设置帧率、启动和暂停对象。
使用起来很简单,只需要创建一个实例,并设置回调函数和所需的帧率。
整个逻辑都在内部处理,非常简单。
此外,还可以通过触发页面回流来实现更快的页面刷新,以及在本机fps速率以下的速率下不会注意到多个页面回流。
解决方法:
// 控制帧率的对象 function FpsCtrl(fps, callback) { var delay = 1000 / fps, // 计算每帧的时间 time = null, // 开始时间 frame = -1, // 帧数 tref; // requestAnimationFrame的时间参考 function loop(timestamp) { if (time === null) time = timestamp; // 初始化开始时间 var seg = Math.floor((timestamp - time) / delay); // 计算帧数 if (seg > frame) { // 是否进入下一帧 frame = seg; // 更新帧数 callback({ // 回调函数 time: timestamp, frame: frame }) } tref = requestAnimationFrame(loop) } // 设置帧率 this.frameRate = function(newfps) { if (!arguments.length) return fps; fps = newfps; delay = 1000 / fps; frame = -1; time = null; }; // 启动对象 this.start = function() { if (!this.isPlaying) { this.isPlaying = true; tref = requestAnimationFrame(loop); } }; // 暂停对象 this.pause = function() { if (this.isPlaying) { cancelAnimationFrame(tref); this.isPlaying = false; time = null; frame = -1; } }; } // 创建实例并设置回调函数和帧率 var fc = new FpsCtrl(24, function(e) { // 每帧的渲染逻辑 }); // 启动对象 fc.start();
这样就可以通过控制帧率来实现更加流畅的动画效果。