Kontrolowanie fps za pomocą requestAnimationFrame?

Wydaje się, że {[1] } jest de facto sposobem na ożywienie rzeczy teraz. To działało całkiem dobrze dla mnie w większości przypadków, ale teraz próbuję zrobić kilka animacji canvas i zastanawiałem się: czy jest jakiś sposób, aby upewnić się, że działa w określonych fps? Rozumiem, że celem rAF jest konsekwentnie płynne animacje, i może ryzykuję, że moja animacja będzie niepewna, ale w tej chwili wydaje się, że działa z drastycznie różnymi prędkościami dość arbitralnie i zastanawiam się, czy istnieje sposób, by to jakoś zwalczyć.

Użyłbym setInterval ale chcę optymalizacji, które oferuje rAF (szczególnie automatycznie zatrzymuje się, gdy zakładka jest w centrum uwagi).

Jakby ktoś chciał spojrzeć na mój kod, to raczej:

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

Gdzie Węzeł.drawFlash () to tylko jakiś kod, który określa promień na podstawie zmiennej counter, a następnie rysuje okrąg.

Author: Lee Taylor, 2013-11-04

8 answers

Jak przycisnąć requestAnimationFrame do określonej liczby klatek na sekundę

Dławienie Demo przy 5 FPS: http://jsfiddle.net/m1erickson/CtsY3/

Ta metoda działa poprzez sprawdzenie czasu jaki upłynął od wykonania ostatniej pętli ramki.

Twój kod rysunkowy jest wykonywany tylko wtedy, gdy upłynął określony interwał FPS.

Pierwsza część kodu ustawia zmienne używane do obliczania czasu, który upłynął.

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

I ten kod jest rzeczywisty requestAnimationFrame loop, który rysuje na podanym FPS.

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

    }
}
 127
Author: markE,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-19 00:52:18

Aktualizacja 2016/6

[[20]}problem z dławieniem liczby klatek na sekundę polega na tym, że ekran ma stałą szybkość aktualizacji, zwykle 60 FPS.

Jeśli chcemy 24 FPS nigdy nie uzyskamy prawdziwego 24 fps na ekranie, możemy to określić jako takie, ale nie pokazać, ponieważ monitor może wyświetlać zsynchronizowane klatki tylko przy 15 fps, 30 fps lub 60 fps (niektóre monitory również 120 fps).

Jednak dla celów czasowych możemy obliczyć i zaktualizować, gdy to możliwe.

Możesz zbudować całą logikę do kontrolowania liczby klatek na sekundę poprzez hermetyzację obliczeń i wywołań zwrotnych w obiekt:

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

Następnie dodaj kontroler i Kod konfiguracji:

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

Użycie

Staje się to bardzo proste - teraz wszystko, co musimy zrobić, to stworzyć instancję, ustawiając funkcję zwrotną i żądaną liczbę klatek na sekundę w następujący sposób:

var fc = new FpsCtrl(24, function(e) {
     // render each frame here
  });
W związku z tym, że nie jest to możliwe, nie jest to możliwe.]}
fc.start();

To jest to, cała logika jest obsługiwana wewnętrznie.

Demo

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>

Stara odpowiedź

Głównym celem {[11] } jest synchronizacja aktualizacji z częstotliwością odświeżania monitora. Wymaga to animacji w klatkach na monitorze lub jego współczynniku(np. 60, 30, 15 FPS dla typowej częstotliwości odświeżania @ 60 Hz).

Jeśli chcesz mieć więcej arbitralnych FPS, nie ma sensu używać rAF, ponieważ liczba klatek na sekundę nigdy nie będzie odpowiadać częstotliwości aktualizacji monitora (tylko ramka tu i tam), które po prostu nie mogą dać płynnej animacji (jak w przypadku wszystkich powtórek klatek) i równie dobrze możesz użyć setTimeout lub setInterval.

Jest to również dobrze znany problem w profesjonalnej branży wideo, gdy chcesz odtwarzać wideo w różnych klatek na sekundę, a następnie urządzenie wyświetlające go odświeżać. Zastosowano wiele technik, takich jak mieszanie ramek i złożone ponowne tworzenie ramek pośrednich w oparciu o wektory ruchu, ale w przypadku canvas te techniki nie są dostępne, a wynikiem zawsze będzie jerky wideo.
var FPS = 24;  /// "silver screen"
var isPlaying = true;

function loop() {
    if (isPlaying) setTimeout(loop, 1000 / FPS);

    ... code for frame here
}

Powód, dla którego umieszczamy setTimeout Po pierwsze (i dlaczego jakieś miejsce rAF Po pierwsze, gdy używane jest poly-fill) jest to, że będzie to dokładniejsze, ponieważ setTimeout będzie kolejkować Zdarzenie natychmiast po uruchomieniu pętli, tak że bez względu na to, ile czasu użyje pozostały kod (pod warunkiem, że nie przekroczy interwału timeout) następne wywołanie będzie w interwale, który reprezentuje (dla czystego rAF nie jest to niezbędne, ponieważ rAF spróbuje przeskoczyć do następnej klatki w każdym przypadku).

Warto również zauważyć, że umieszczenie go na pierwszym miejscu spowoduje również ryzyko zestawiania wywołań tak, jak w przypadku setInterval. setInterval może być nieco bardziej dokładne dla tego zastosowania.

I możesz użyć setInterval zamiast poza pętli, aby zrobić to samo.

var FPS = 29.97;   /// NTSC
var rememberMe = setInterval(loop, 1000 / FPS);

function loop() {

    ... code for frame here
}

I zatrzymać pętlę:

clearInterval(rememberMe);

W celu zmniejszenia liczby klatek na sekundę, gdy karta zostanie zamazana, możesz dodać czynnik taki:

var isFocus = 1;
var FPS = 25;

function loop() {
    setTimeout(loop, 1000 / (isFocus * FPS)); /// note the change here

    ... code for frame here
}

window.onblur = function() {
    isFocus = 0.5; /// reduce FPS to half   
}

window.onfocus = function() {
    isFocus = 1; /// full FPS
}

W ten sposób można zmniejszyć FPS do 1/4 itd.

 29
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-19 02:15:42

Proponuję zawinąć rozmowę do requestAnimationFrame w setTimeout. Jeśli wywołasz setTimeout z funkcji, z której zażądano klatki animacji, nie spełniasz celu requestAnimationFrame. Ale jeśli wywołasz requestAnimationFrame z wewnątrz setTimeout działa to płynnie:

var fps = 25
function animate() {
  setTimeout(function() {
    requestAnimationFrame(animate);
  }, 1000 / fps);
}
 21
Author: Luke Taylor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-25 02:09:38

To są wszystkie dobre pomysły w teorii, dopóki nie wejdziesz głęboko. Problem w tym, że nie da się zdławić RAF-a bez jego de-synchronizacji. pozwala to działać z pełną prędkością i aktualizować dane w osobnej pętli, albo nawet osobny wątek!

Tak, powiedziałem to. Możesz robić wielowątkowy JavaScript w przeglądarce!

Są dwie metody, które Wiem, że działają bardzo dobrze bez Janka, używając znacznie mniej soku i tworząc mniej ciepła. Dokładność pomiaru czasu w skali człowieka i wydajność maszyny są wynikiem netto.

Przepraszam, jeśli to trochę gadatliwe, ale proszę bardzo...

Metoda 1: Aktualizacja danych przez setInterval, A Grafiki przez RAF.

Użyj oddzielnego setInterval do aktualizacji wartości translacji i obrotu, fizyki, kolizji itp. Zachowaj te wartości w obiekcie dla każdego animowanego elementu. Przypisz łańcuch transform do zmiennej w obiekcie each setInterval 'frame'. Trzymaj te obiekty w tablicy. Ustaw interwał na żądany fps w ms: ms=(1000/fps). Zapewnia to stały zegar, który pozwala na taką samą liczbę klatek na sekundę na każdym urządzeniu, niezależnie od prędkości RAF. nie przypisuj przekształceń do elementów tutaj!

W pętli requestAnimationFrame, iteruj przez tablicę za pomocą pętli old-school for-- Nie używaj nowszych formularzy tutaj, są one powolne!

for(var i=0; i<sprite.length-1; i++){  rafUpdate(sprite[i]);  }

W funkcji rafUpdate, Pobierz łańcuch transform z Twój obiekt js w tablicy i jego ID elementów. Powinieneś już mieć swoje "sprite" elementy dołączone do zmiennej lub łatwo dostępne za pomocą innych środków, aby nie tracić czasu " get " - ing je w RAF. Trzymanie ich w obiekcie nazwanym po identyfikatorze html działa całkiem dobrze. Ustaw tę część, zanim trafi do twojego SI lub RAF.

Użyj RAF, aby zaktualizować swoje transformaty tylko , używaj tylko przekształceń 3D (nawet dla 2D) i ustaw css "will-change: transform;" na elementach to się zmieni. Dzięki temu Twoje transformaty są w jak największym stopniu zsynchronizowane z natywną częstotliwością odświeżania, uruchamiają procesor GPU i informują przeglądarkę, gdzie należy się najbardziej skoncentrować.

Więc powinieneś mieć coś takiego jak ten pseudokod...

// refs to elements to be transformed, kept in an array
var element = [
   mario: document.getElementById('mario'),
   luigi: document.getElementById('luigi')
   //...etc.
]

var sprite = [  // read/write this with SI.  read-only from RAF
   mario: { id: mario  ....physics data, id, and updated transform string (from SI) here  },
   luigi: {  id: luigi  .....same  }
   //...and so forth
] // also kept in an array (for efficient iteration)

//update one sprite js object
//data manipulation, CPU tasks for each sprite object
//(physics, collisions, and transform-string updates here.)
//pass the object (by reference).
var SIupdate = function(object){
  // get pos/rot and update with movement
  object.pos.x += object.mov.pos.x;  // example, motion along x axis
  // and so on for y and z movement
  // and xyz rotational motion, scripted scaling etc

  // build transform string ie
  object.transform =
   'translate3d('+
     object.pos.x+','+
     object.pos.y+','+
     object.pos.z+
   ') '+

   // assign rotations, order depends on purpose and set-up. 
   'rotationZ('+object.rot.z+') '+
   'rotationY('+object.rot.y+') '+
   'rotationX('+object.rot.x+') '+

   'scale3d('.... if desired
  ;  //...etc.  include 
}


var fps = 30; //desired controlled frame-rate


// CPU TASKS - SI psuedo-frame data manipulation
setInterval(function(){
  // update each objects data
  for(var i=0; i<sprite.length-1; i++){  SIupdate(sprite[i]);  }
},1000/fps); //  note ms = 1000/fps


// GPU TASKS - RAF callback, real frame graphics updates only
var rAf = function(){
  // update each objects graphics
  for(var i=0; i<sprite.length-1; i++){  rAF.update(sprite[i])  }
  window.requestAnimationFrame(rAF); // loop
}

// assign new transform to sprite's element, only if it's transform has changed.
rAF.update = function(object){     
  if(object.old_transform !== object.transform){
    element[object.id].style.transform = transform;
    object.old_transform = object.transform;
  }
} 

window.requestAnimationFrame(rAF); // begin RAF

Dzięki temu aktualizacje obiektów danych i ciągów przekształceń są synchronizowane z żądaną częstotliwością klatek w układzie SI, a rzeczywiste przydziały przekształceń w układzie RAF są synchronizowane z częstotliwością odświeżania GPU. Więc rzeczywiste aktualizacje grafiki są tylko w RAF, ale zmiany danych i budowanie ciągu transformacji są w SI, więc nie ma jankies, ale "czas" płynie z żądaną szybkością klatek.


Przepływ:

[setup js sprite objects and html element object references]

[setup RAF and SI single-object update functions]

[start SI at percieved/ideal frame-rate]
  [iterate through js objects, update data transform string for each]
  [loop back to SI]

[start RAF loop]
  [iterate through js objects, read object's transform string and assign it to it's html element]
  [loop back to RAF]

Metoda 2. Umieść SI w Web-workerze. Ta jest fajna i gładka!

Tak samo jak Metoda 1, ale umieść SI w Web-workerze. To będzie działać na zupełnie osobnym wątku, pozostawiając stronę do czynienia tylko z RAF i UI. Przekaż tablicę sprite 'ów w tę i z powrotem jako "obiekt przekazywalny". To jest buko szybko. Nie potrzeba czasu, aby klonować lub serializować, ale to nie jest tak, jak przechodzenie przez odniesienie, ponieważ odniesienie z drugiej strony jest zniszczone, więc musisz mieć obie strony przechodzą na drugą stronę i aktualizować je tylko wtedy, gdy są obecne, coś jak przekazywanie notatki tam iz powrotem ze swoją dziewczyną w liceum.

Tylko jeden może czytać i pisać na raz. Jest to w porządku, o ile sprawdzają, czy nie jest niezdefiniowany, aby uniknąć błędu. RAF jest szybki i kopnie go z powrotem natychmiast przejrzyj kilka ramek GPU, sprawdzając, czy zostały jeszcze odesłane. SI w Web-workerze będzie miał tablicę sprite ' ów przez większość czasu i zaktualizuje dane pozycyjne, ruchowe i fizyczne, a także utworzy nowy ciąg transformacji, a następnie przekaże go z powrotem do RAF na stronie.

Jest to najszybszy sposób, jaki znam do animowania elementów za pomocą skryptu. Dwie funkcje będą działać jako dwa oddzielne programy, w dwóch oddzielnych wątkach, korzystając z procesor wielordzeniowy jest w taki sposób, że pojedynczy skrypt js nie Wielowątkowa animacja javascript.

I zrobi to płynnie bez Janka, ale z rzeczywistą określoną liczbą klatek na sekundę, z bardzo małą rozbieżnością.


Wynik:

Jedna z tych dwóch metod zapewni, że skrypt będzie działał z tą samą prędkością na każdym komputerze, telefonie, tablecie itp. (oczywiście w ramach możliwości urządzenia i przeglądarki).

 7
Author: jdmayfield,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-26 03:08:48

Pomijanie requestAnimationFrame cause not smooth (desired) animation at custom fps.

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

Oryginalny kod by @tavnab.

 2
Author: befzz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-16 10:05:22

Jak łatwo Dławić konkretny FPS:

// timestamps are ms passed since document creation.
// lastTimestamp can be initialized to 0, if main loop is executed immediately
var lastTimestamp = 0,
    maxFPS = 30,
    timestep = 1000 / maxFPS; // ms for each frame

function main(timestamp) {
    window.requestAnimationFrame(main);

    // skip if timestep ms hasn't passed since last frame
    if (timestamp - lastTimestamp < timestep) return;

    lastTimestamp = timestamp;

    // draw frame here
}

window.requestAnimationFrame(main);

Source: A Detailed Explanation of JavaScript Game Loops and Timing by Isaac Sukin

 2
Author: Rustem Kakimov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-06 06:51:02

Zawsze robię to w bardzo prosty sposób, bez mieszania ze znacznikami czasu:

var fps, eachNthFrame, frameCount;

fps = 30;

//This variable specifies how many frames should be skipped.
//If it is 1 then no frames are skipped. If it is 2, one frame 
//is skipped so "eachSecondFrame" is renderd.
eachNthFrame = Math.round((1000 / fps) / 16.66);

//This variable is the number of the current frame. It is set to eachNthFrame so that the 
//first frame will be renderd.
frameCount = eachNthFrame;

requestAnimationFrame(frame);

//I think the rest is self-explanatory
fucntion frame() {
  if (frameCount == eachNthFrame) {
    frameCount = 0;
    animate();
  }
  frameCount++;
  requestAnimationFrame(frame);
}
 1
Author: Samer Alkhabbaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-28 16:40:28

Oto dobre wyjaśnienie, które znalazłem: CreativeJS.com , aby zawinąć wywołanie setTimeou) wewnątrz funkcji przekazanej do requestAnimationFrame. Moim zmartwieniem z "zwykłym" requestionAnimationFrame byłoby: "co jeśli tylko chcę aby animować trzy razy na sekundę?"Nawet z requestAnimationFrame (w przeciwieństwie do setTimeout) jest to, że nadal marnuje (trochę) ilość "energii" (co oznacza, że kod przeglądarki coś robi i prawdopodobnie spowalnia system) 60 lub 120 lub jednak wiele razy na sekundę, w przeciwieństwie do tylko dwa lub trzy razy na sekundę (jak możesz chcieć).

Większość czasu uruchamiam moje przeglądarki z JavaScript intencyjnie off właśnie z tego powodu. Ale używam Yosemite 10.10.3 i myślę, że jest jakiś problem z timerem - przynajmniej na moim starym systemie(stosunkowo stary-czyli 2011).

 0
Author: Jim Witte,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-02 23:24:53