使用requestAnimationFrame控制fps?

看起来像requestAnimationFrame是现在animation事件的事实上的方式。 它在大多数情况下工作得非常好,但现在我正在尝试做一些canvasanimation,我想知道:有没有办法确保它以某个fps运行? 我明白,RAF的目的是始终如一地保持stream畅的animation效果,而且我可能会冒着制作animation波涛汹涌的风险,但是现在它似乎以非常不同的速度运行,而且我想知道是否有办法打击不知何故。

我会使用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()只是一些基于计数器variables确定半径的代码,然后绘制一个圆圈。

谢谢!

如何将requestAnimationFrame调整为特定的帧速率

演示在5 FPS节stream: http : //jsfiddle.net/m1erickson/CtsY3/

此方法通过testing自执行最后一个帧循环以来所经过的时间来工作。

您的绘图代码仅在您指定的FPS间隔已过时执行。

代码的第一部分设置了一些用来计算stream逝时间的variables。

 var stop = false; var frameCount = 0; var $results = $("#results"); var fps, fpsInterval, startTime, now, then, elapsed; // initialize the timer variables and start the animation function startAnimating(fps) { fpsInterval = 1000 / fps; then = Date.now(); startTime = then; animate(); } 

而这个代码是在你指定的FPS上绘制的实际的requestAnimationFrame循环。

 // the animation loop calculates time elapsed since the last loop // and only draws if your specified fps interval is achieved function animate() { // request another frame requestAnimationFrame(animate); // calc elapsed time since last loop now = Date.now(); elapsed = now - then; // if enough time has elapsed, draw the next frame if (elapsed > fpsInterval) { // Get ready for next frame by setting then=now, but also adjust for your // specified fpsInterval not being a multiple of RAF's interval (16.7ms) then = now - (elapsed % fpsInterval); // Put your drawing code here } } 

更新2016/6

限制帧频的问题是屏幕具有恒定的更新速率,通常为60 FPS。

如果我们想要24 FPS我们将永远不会在屏幕上获得真正的24 fps,我们可以这样做,但不显示它,因为显示器只能以15 fps,30 fps或60 fps显示同步帧(有些显示器也是120 fps )。

但是,为了计时目的,我们可以在可能的情况下计算和更新

您可以通过将计算和callback封装到对象中来构build控制帧速率的所有逻辑:

 function FpsCtrl(fps, callback) { var delay = 1000 / fps, // calc. time per frame time = null, // start time frame = -1, // frame count tref; // rAF time reference function loop(timestamp) { if (time === null) time = timestamp; // init start time var seg = Math.floor((timestamp - time) / delay); // calc frame no. if (seg > frame) { // moved to next frame? frame = seg; // update callback({ // callback function time: timestamp, frame: frame }) } tref = requestAnimationFrame(loop) } } 

然后添加一些控制器和configuration代码:

 // play status this.isPlaying = false; // set frame-rate this.frameRate = function(newfps) { if (!arguments.length) return fps; fps = newfps; delay = 1000 / fps; frame = -1; time = null; }; // enable starting/pausing of the object 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; } }; 

用法

它变得非常简单 – 现在,我们所要做的就是通过设置callback函数和期望的帧速率来创build一个实例,就像这样:

 var fc = new FpsCtrl(24, function(e) { // render each frame here }); 

然后开始(如果需要,这可能是默认行为):

 fc.start(); 

就是这样,所有的逻辑都是在内部处理的。

演示

 var ctx = c.getContext("2d"), pTime = 0, mTime = 0, x = 0; ctx.font = "20px sans-serif"; // update canvas with some information and animation var fps = new FpsCtrl(12, function(e) { ctx.clearRect(0, 0, c.width, c.height); ctx.fillText("FPS: " + fps.frameRate() + " Frame: " + e.frame + " Time: " + (e.time - pTime).toFixed(1), 4, 30); pTime = e.time; var x = (pTime - mTime) * 0.1; if (x > c.width) mTime = pTime; ctx.fillRect(x, 50, 10, 10) }) // start the loop fps.start(); // UI bState.onclick = function() { fps.isPlaying ? fps.pause() : fps.start(); }; sFPS.onchange = function() { fps.frameRate(+this.value) }; function FpsCtrl(fps, callback) { var delay = 1000 / fps, time = null, frame = -1, tref; 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.isPlaying = false; 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; } }; } 
 body {font:16px sans-serif} 
 <label>Framerate: <select id=sFPS> <option>12</option> <option>15</option> <option>24</option> <option>25</option> <option>29.97</option> <option>30</option> <option>60</option> </select></label><br> <canvas id=c height=60></canvas><br> <button id=bState>Start/Stop</button> 

我build议将你的调用包装在setTimeout requestAnimationFrame中。 如果您从请求animation帧的函数中调用setTimeout ,则会requestAnimationFrame的用途。 但是如果你从setTimeout调用requestAnimationFrame ,它可以正常工作:

 var fps = 25 function animate() { setTimeout(function() { requestAnimationFrame(animate); }, 1000 / fps); } 

跳过requestAnimationFrame会导致自定义fps 不平滑 (需要)animation。

 // Input/output DOM elements var $results = $("#results"); var $fps = $("#fps"); var $period = $("#period"); // Array of FPS samples for graphing // Animation state/parameters var fpsInterval, lastDrawTime, frameCount_timed, frameCount, lastSampleTime, currentFps=0, currentFps_timed=0; var intervalID, requestID; // Setup canvas being animated var canvas = document.getElementById("c"); var canvas_timed = document.getElementById("c2"); canvas_timed.width = canvas.width = 300; canvas_timed.height = canvas.height = 300; var ctx = canvas.getContext("2d"); var ctx2 = canvas_timed.getContext("2d"); // Setup input event handlers $fps.on('click change keyup', function() { if (this.value > 0) { fpsInterval = 1000 / +this.value; } }); $period.on('click change keyup', function() { if (this.value > 0) { if (intervalID) { clearInterval(intervalID); } intervalID = setInterval(sampleFps, +this.value); } }); function startAnimating(fps, sampleFreq) { ctx.fillStyle = ctx2.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx2.fillRect(0, 0, canvas.width, canvas.height); ctx2.font = ctx.font = "32px sans"; fpsInterval = 1000 / fps; lastDrawTime = performance.now(); lastSampleTime = lastDrawTime; frameCount = 0; frameCount_timed = 0; animate(); intervalID = setInterval(sampleFps, sampleFreq); animate_timed() } function sampleFps() { // sample FPS var now = performance.now(); if (frameCount > 0) { currentFps = (frameCount / (now - lastSampleTime) * 1000).toFixed(2); currentFps_timed = (frameCount_timed / (now - lastSampleTime) * 1000).toFixed(2); $results.text(currentFps + " | " + currentFps_timed); frameCount = 0; frameCount_timed = 0; } lastSampleTime = now; } function drawNextFrame(now, canvas, ctx, fpsCount) { // Just draw an oscillating seconds-hand var length = Math.min(canvas.width, canvas.height) / 2.1; var step = 15000; var theta = (now % step) / step * 2 * Math.PI; var xCenter = canvas.width / 2; var yCenter = canvas.height / 2; var x = xCenter + length * Math.cos(theta); var y = yCenter + length * Math.sin(theta); ctx.beginPath(); ctx.moveTo(xCenter, yCenter); ctx.lineTo(x, y); ctx.fillStyle = ctx.strokeStyle = 'white'; ctx.stroke(); var theta2 = theta + 3.14/6; ctx.beginPath(); ctx.moveTo(xCenter, yCenter); ctx.lineTo(x, y); ctx.arc(xCenter, yCenter, length*2, theta, theta2); ctx.fillStyle = "rgba(0,0,0,.1)" ctx.fill(); ctx.fillStyle = "#000"; ctx.fillRect(0,0,100,30); ctx.fillStyle = "#080"; ctx.fillText(fpsCount,10,30); } // redraw second canvas each fpsInterval (1000/fps) function animate_timed() { frameCount_timed++; drawNextFrame( performance.now(), canvas_timed, ctx2, currentFps_timed); setTimeout(animate_timed, fpsInterval); } function animate(now) { // request another frame requestAnimationFrame(animate); // calc elapsed time since last loop var elapsed = now - lastDrawTime; // if enough time has elapsed, draw the next frame if (elapsed > fpsInterval) { // Get ready for next frame by setting lastDrawTime=now, but... // Also, adjust for fpsInterval not being multiple of 16.67 lastDrawTime = now - (elapsed % fpsInterval); frameCount++; drawNextFrame(now, canvas, ctx, currentFps); } } startAnimating(+$fps.val(), +$period.val()); 
 input{ width:100px; } #tvs{ color:red; padding:0px 25px; } H3{ font-weight:400; } 
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <h3>requestAnimationFrame skipping <span id="tvs">vs.</span> setTimeout() redraw</h3> <div> <input id="fps" type="number" value="33"/> FPS: <span id="results"></span> </div> <div> <input id="period" type="number" value="1000"/> Sample period (fps, ms) </div> <canvas id="c"></canvas><canvas id="c2"></canvas> 

下面是我发现的一个很好的解释: CreativeJS.com ,在传递给requestAnimationFrame的函数内部包装一个setTimeou)调用。 我对“简单”的animation框架的关注是:“如果我只想让它animation三次,那该怎么办?” 即使使用requestAnimationFrame(而不是setTimeout),它仍然会浪费(一些)“能量”(意味着浏览器代码正在做某件事情,可能会减慢系统的速度)60或120,或者每秒很多次,反对每秒只有两三次(正如你可能想要的)。

大多数情况下,我只是因为这个原因才用JavaScript运行我的浏览器。 但是,我使用的是优胜美地10.10.3,我认为它有一些计时器的问题 – 至less在我的旧系统(相对较老 – 意思是2011)。