Contrôle de fps avec requestAnimationFrame?

Il semble que requestAnimationFrame est la manière de facto d'animer les choses maintenant. Cela m'a beaucoup fonctionné pour la plupart, mais en ce moment, j'essaie de faire des animations en toile et je me demandais: est-ce qu'il y a un moyen de s'assurer que ça fonctionne à une certaine fps? Je comprends que le but de rAF est d'organiser des animations en douceur, et je risquerais de faire vibrer mon animation, mais maintenant, il semble qu'il fonctionne à des vitesses drastiquement différentes, et je me demande s'il y a un moyen de combattre De quelque façon que ce soit.

J'utiliserais setInterval mais je veux les optimisations offertes par rAF (surtout l'arrêt automatique lorsque l'onglet est en mise au point).

Dans le cas où quelqu'un veut regarder mon code, c'est à peu près:

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

Où Node.drawFlash () est juste un code qui détermine le rayon basé sur une variable de compteur, puis dessine un cercle.

Merci!

    Comment accélérer demandeAnimationFrame à une fréquence image spécifique

    Réduction de la démo à 5 FPS: http://jsfiddle.net/m1erickson/CtsY3/

    Cette méthode fonctionne en testant le temps écoulé depuis l'exécution de la dernière boucle de trame.

    Votre code de dessin n'exécute que lorsque votre intervalle FPS spécifié s'est écoulé.

    La première partie du code définit certaines variables utilisées pour calculer le temps écoulé.

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

    Et ce code est la boucle de demande de simulation d'image qui s'inspire de votre FPS spécifié.

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

    Mise à jour 2016/6

    Le problème de l'accélération de la fréquence d'images est que l'écran a un taux de mise à jour constant, généralement 60 FPS.

    Si nous voulons 24 FPS, nous n'obtiendrons jamais les vraies 24 images par seconde, nous pouvons l'écouter en tant que tel, mais pas le montrer, car le moniteur ne peut afficher que les images synchronisées à 15 fps, 30 fps ou 60 fps (certains moniteurs aussi 120 fps ).

    Cependant, pour le calendrier, nous pouvons calculer et mettre à jour si possible.

    Vous pouvez construire toute la logique pour contrôler le taux de trame en encapsulant des calculs et des rappels dans un objet:

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

    Ensuite, ajoutez un code de contrôleur et de 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; } }; 

    Usage

    Il devient très simple – maintenant, tout ce que nous devons faire est de créer une instance en définissant la fonction de rappel et la cadence d'image souhaitée comme suit:

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

    Ensuite, démarrez (ce qui pourrait être le comportement par défaut si vous le souhaitez):

     fc.start(); 

    C'est tout, toute la logique est traitée en interne.

    Démonstration

     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> 

    Je suggère d'enrouler votre appel pour requestAnimationFrame dans un setTimeout . Si vous appelez setTimeout partir de la fonction à partir de laquelle vous avez demandé le cadre d'animation, vous êtes en train de vaincre le but de requestAnimationFrame . Mais si vous appelez requestAnimationFrame partir de setTimeout cela fonctionne en douceur:

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

    Ignorer demandeAnimationFrame ne cause pas une animation lisse (souhaitée) à des fps personnalisés.

     // 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> 

    Voici une bonne explication que j'ai trouvée: CreativeJS.com , pour envelopper un setTimeou) dans la fonction passée à requestAnimationFrame. Mon souci d'une requête «simple» serait «Qu'en ferait-il si je veux seulement que cela anime trois fois par seconde? Même avec requestAnimationFrame (par opposition à setTimeout), c'est qu'il gaspille encore (certains) quantité d'énergie (ce qui signifie que le code du navigateur fait quelque chose et peut-être ralentit le système) 60 ou 120 ou bien plusieurs fois par seconde, comme S'opposant à seulement deux ou trois fois par seconde (comme vous voudrez peut-être).

    La plupart du temps, je cours sur mes navigateurs avec JavaScript intentially off pour cette raison. Mais, j'utilise Yosemite 10.10.3, et je pense qu'il y a un problème de minuterie – au moins sur mon ancien système (relativement ancien – ce qui signifie 2011).