Es scheint, requestAnimationFrame
als wäre dies de facto der Weg, Dinge jetzt zu animieren. Es hat größtenteils ziemlich gut für mich funktioniert, aber im Moment versuche ich einige Canvas-Animationen zu machen und habe mich gefragt: Gibt es eine Möglichkeit, um sicherzustellen, dass es mit einer bestimmten Geschwindigkeit läuft? Ich verstehe, dass der Zweck von rAF darin besteht, Animationen durchgehend flüssig zu machen, und ich könnte das Risiko eingehen, dass meine Animation abgehackt wird, aber im Moment scheint sie ziemlich willkürlich mit drastisch unterschiedlichen Geschwindigkeiten zu laufen, und ich frage mich, ob es einen Weg gibt, sie zu bekämpfen das irgendwie.
Ich würde verwenden, setInterval
aber ich möchte die Optimierungen, die rAF bietet (insbesondere das automatische Stoppen, wenn die Registerkarte scharfgestellt ist).
Wenn jemand meinen Code ansehen möchte, ist es so ziemlich:
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();
}
}
Wobei Node.drawFlash () nur ein Code ist, der den Radius anhand einer Zählervariablen bestimmt und dann einen Kreis zeichnet.
quelle
requestAnimationFrame
ist (wie der Name schon sagt), dass ein Animationsrahmen nur dann angefordert wird, wenn er benötigt wird. Angenommen, Sie zeigen eine statische schwarze Leinwand. Sie sollten 0 fps erhalten, da kein neuer Frame benötigt wird. Wenn Sie jedoch eine Animation anzeigen, die 60 fps erfordert, sollten Sie diese auch erhalten.rAF
erlaubt nur, nutzlose Frames zu "überspringen" und dann die CPU zu sparen.Antworten:
So drosseln Sie requestAnimationFrame auf eine bestimmte Bildrate
Demo-Drosselung mit 5 FPS: http://jsfiddle.net/m1erickson/CtsY3/
Diese Methode testet die seit dem Ausführen der letzten Frame-Schleife verstrichene Zeit.
Ihr Zeichnungscode wird nur ausgeführt, wenn das angegebene FPS-Intervall abgelaufen ist.
Der erste Teil des Codes legt einige Variablen fest, die zur Berechnung der verstrichenen Zeit verwendet werden.
Und dieser Code ist die eigentliche requestAnimationFrame-Schleife, die mit Ihrem angegebenen FPS gezeichnet wird.
quelle
Update 2016/6
Das Problem bei der Drosselung der Bildrate besteht darin, dass der Bildschirm eine konstante Aktualisierungsrate aufweist, normalerweise 60 FPS.
Wenn wir 24 FPS wollen, werden wir nie die wahren 24 fps auf dem Bildschirm sehen, wir können sie als solche zeitlich festlegen, aber nicht anzeigen, da der Monitor nur synchronisierte Frames mit 15 fps, 30 fps oder 60 fps anzeigen kann (einige Monitore auch 120 fps ).
Aus zeitlichen Gründen können wir jedoch nach Möglichkeit berechnen und aktualisieren.
Sie können die gesamte Logik zur Steuerung der Bildrate erstellen, indem Sie Berechnungen und Rückrufe in ein Objekt einkapseln:
Fügen Sie dann einen Controller- und Konfigurationscode hinzu:
Verwendung
Es wird sehr einfach - jetzt müssen wir nur noch eine Instanz erstellen, indem wir die Rückruffunktion und die gewünschte Bildrate wie folgt einstellen:
Starten Sie dann (was bei Bedarf das Standardverhalten sein kann):
Das war's, die gesamte Logik wird intern gehandhabt.
Demo
Alte Antwort
Der Hauptzweck von
requestAnimationFrame
besteht darin, Aktualisierungen mit der Aktualisierungsrate des Monitors zu synchronisieren. Dazu müssen Sie die FPS des Monitors oder einen Faktor davon animieren (dh 60, 30, 15 FPS für eine typische Bildwiederholfrequenz bei 60 Hz).Wenn Sie eine willkürlichere FPS wünschen, macht es keinen Sinn, rAF zu verwenden, da die Bildrate sowieso nie mit der Aktualisierungsfrequenz des Monitors übereinstimmt (nur ein Bild hier und da), was einfach keine reibungslose Animation ermöglichen kann (wie bei allen Bild-Timings) ) und Sie können genauso gut
setTimeout
odersetInterval
stattdessen verwenden.Dies ist auch ein bekanntes Problem in der professionellen Videobranche, wenn Sie ein Video mit einer anderen FPS als dem Gerät abspielen möchten, bei dem es aktualisiert wird. Es wurden viele Techniken verwendet, wie z. B. das Überblenden von Bildern und das komplexe Neugestalten von Zwischenbildern basierend auf Bewegungsvektoren. Bei Leinwand sind diese Techniken jedoch nicht verfügbar, und das Ergebnis ist immer ein ruckeliges Video.
Der Grund, warum wir an
setTimeout
erster Stelle stehen (und warum anrAF
erster Stelle, wenn eine Polyfüllung verwendet wird), ist, dass dies genauer ist, dasetTimeout
ein Ereignis sofort beim Start der Schleife in die Warteschlange gestellt wird, sodass unabhängig davon, wie viel Zeit der verbleibende Code benötigt (vorausgesetzt, das Timeout-Intervall wird nicht überschritten) Der nächste Aufruf erfolgt in dem Intervall, das er darstellt (für reines rAF ist dies nicht unbedingt erforderlich, da rAF in jedem Fall versucht, auf den nächsten Frame zu springen).Es ist auch erwähnenswert, dass das Platzieren an erster Stelle auch das Risiko birgt, dass sich Anrufe wie bei stapeln
setInterval
.setInterval
kann für diese Verwendung etwas genauer sein.Und Sie können
setInterval
stattdessen außerhalb der Schleife verwenden, um dasselbe zu tun.Und um die Schleife zu stoppen:
Um die Bildrate zu verringern, wenn die Registerkarte unscharf wird, können Sie einen Faktor wie den folgenden hinzufügen:
Auf diese Weise können Sie die FPS auf 1/4 usw. reduzieren.
quelle
requestAnimationFrame
besteht, DOM-Vorgänge (Lesen / Schreiben) zu synchronisieren. Wenn Sie sie nicht verwenden, wird die Leistung beim Zugriff auf das DOM beeinträchtigt, da Vorgänge nicht in die Warteschlange gestellt werden, um zusammen ausgeführt zu werden, und das unnötige Neulackieren des Layouts erzwingen.Ich schlage vor, Ihren Anruf
requestAnimationFrame
in asetTimeout
. Wenn SiesetTimeout
innerhalb der Funktion aufrufen, von der Sie den Animationsrahmen angefordert haben, verlieren Sie den Zweck vonrequestAnimationFrame
. Aber wenn SierequestAnimationFrame
von innen anrufensetTimeout
, funktioniert es reibungslos:quelle
Das sind alles gute Ideen in der Theorie, bis Sie tief gehen. Das Problem ist, dass Sie eine RAF nicht drosseln können, ohne sie zu de-synchronisieren, wodurch ihr eigentlicher Zweck für die Existenz zunichte gemacht wird. So können Sie es bei voller Geschwindigkeit laufen lassen und Ihre Daten in einer separaten Schleife aktualisieren , oder sogar einem separaten Thread!
Ja, ich habe es gesagt. Sie können im Browser Multithread-JavaScript erstellen!
Ich kenne zwei Methoden, die ohne Ruck sehr gut funktionieren, viel weniger Saft verbrauchen und weniger Wärme erzeugen. Genaue Zeitmessung im menschlichen Maßstab und Maschineneffizienz sind das Nettoergebnis.
Entschuldigung, wenn dies ein wenig wortreich ist, aber hier geht ...
Methode 1: Aktualisieren Sie Daten über setInterval und Grafiken über RAF.
Verwenden Sie ein separates setInterval zum Aktualisieren von Übersetzungs- und Rotationswerten, Physik, Kollisionen usw. Behalten Sie diese Werte für jedes animierte Element in einem Objekt. Weisen Sie die Transformationszeichenfolge einer Variablen im Objekt jedes setInterval-Frames zu. Halten Sie diese Objekte in einem Array. Stellen Sie Ihr Intervall in ms auf die gewünschten fps ein: ms = (1000 / fps). Dadurch bleibt eine konstante Uhr erhalten, die auf jedem Gerät unabhängig von der RAF-Geschwindigkeit die gleichen Bilder pro Sekunde zulässt. Ordnen Sie die Transformationen hier nicht den Elementen zu!
Durchlaufen Sie in einer requestAnimationFrame-Schleife Ihr Array mit einer for-Schleife der alten Schule - verwenden Sie hier nicht die neueren Formulare, sie sind langsam!
Rufen Sie in Ihrer Funktion rafUpdate die Transformationszeichenfolge von Ihrem js-Objekt im Array und dessen Element-ID ab. Sie sollten Ihre Sprite-Elemente bereits an eine Variable angehängt haben oder auf andere Weise leicht zugänglich sein, damit Sie keine Zeit verlieren, sie in der RAF abzurufen. Es funktioniert ziemlich gut, sie in einem Objekt zu behalten, das nach der HTML-ID benannt ist. Richten Sie diesen Teil ein, bevor er überhaupt in Ihre SI oder RAF gelangt.
Verwenden Sie die RAF Ihre Transformationen aktualisieren nur , verwenden Sie nur 3D - Transformationen (auch für 2d) und Set css „will-change: verwandeln;“ auf Elemente, die sich ändern werden. Auf diese Weise werden Ihre Transformationen so weit wie möglich mit der nativen Aktualisierungsrate synchronisiert, die GPU aktiviert und dem Browser mitgeteilt, wo er sich am meisten konzentrieren soll.
Sie sollten also so etwas wie diesen Pseudocode haben ...
Dadurch bleiben Ihre Aktualisierungen der Datenobjekte und Transformationszeichenfolgen mit der gewünschten Framerate im SI synchronisiert, und die tatsächlichen Transformationszuweisungen in der RAF werden mit der GPU-Aktualisierungsrate synchronisiert. Die eigentlichen Grafikaktualisierungen befinden sich also nur in der RAF, aber die Änderungen an den Daten und das Erstellen der Transformationszeichenfolge befinden sich in der SI, sodass keine Ruckler, sondern "Zeit" mit der gewünschten Bildrate fließt.
Fließen:
Methode 2. Legen Sie den SI in einen Web-Worker. Dieser ist FAAAST und glatt!
Wie Methode 1, jedoch den SI in Web-Worker einfügen. Es wird dann in einem völlig separaten Thread ausgeführt, sodass die Seite nur für RAF und Benutzeroberfläche verwendet werden kann. Übergeben Sie das Sprite-Array als übertragbares Objekt hin und her. Das ist buko schnell. Das Klonen oder Serialisieren dauert nicht lange, aber es ist nicht so, als würde eine Referenz übergeben, da die Referenz von der anderen Seite zerstört wird. Sie müssen also beide Seiten auf die andere Seite übergeben und sie nur aktualisieren, wenn sie vorhanden sind, sortieren als würde man mit seiner Freundin in der High School eine Notiz hin und her geben.
Es kann immer nur einer lesen und schreiben. Dies ist in Ordnung, solange sie prüfen, ob es nicht undefiniert ist, um einen Fehler zu vermeiden. Die RAF ist SCHNELL und wird sie sofort zurückwerfen und dann eine Reihe von GPU-Frames durchlaufen, um zu überprüfen, ob sie bereits zurückgesendet wurde. Der SI im Web-Worker verfügt die meiste Zeit über das Sprite-Array und aktualisiert Positions-, Bewegungs- und Physikdaten sowie die neue Transformationszeichenfolge und gibt sie dann an die RAF auf der Seite zurück.
Dies ist der schnellste Weg, um Elemente per Skript zu animieren. Die beiden Funktionen werden als zwei separate Programme auf zwei separaten Threads ausgeführt, wobei Multi-Core-CPUs auf eine Weise genutzt werden, die ein einzelnes js-Skript nicht bietet. Multithread-Javascript-Animation.
Und das ohne Ruck, aber mit der tatsächlich angegebenen Bildrate und mit sehr geringer Abweichung.
Ergebnis:
Mit beiden Methoden wird sichergestellt, dass Ihr Skript auf jedem PC, Telefon, Tablet usw. mit der gleichen Geschwindigkeit ausgeführt wird (natürlich im Rahmen der Funktionen des Geräts und des Browsers).
quelle
visibilitychange
Ereignis aufgetreten zu sein .So drosseln Sie einfach auf einen bestimmten FPS:
Quelle: Eine detaillierte Erklärung der JavaScript-Spielschleifen und des Timings von Isaac Sukin
quelle
Das Überspringen von requestAnimationFrame führt zu einer nicht glatten (gewünschten) Animation bei benutzerdefinierten fps.
Originalcode von @tavnab.
quelle
quelle
Ich mache es immer so einfach, ohne mit Zeitstempeln zu spielen:
quelle
Hier ist eine gute Erklärung, die ich gefunden habe: CreativeJS.com , um einen setTimeou) -Aufruf in die an requestAnimationFrame übergebene Funktion zu verpacken. Mein Anliegen bei einem "einfachen" requestionAnimationFrame wäre: "Was ist, wenn ich möchte, dass es nur dreimal pro Sekunde animiert wird?" Selbst bei requestAnimationFrame (im Gegensatz zu setTimeout) wird immer noch (etwas) "Energie" verschwendet (was bedeutet, dass der Browser-Code etwas tut und möglicherweise das System verlangsamt) 60 oder 120 oder wie oft auch immer pro Sekunde im Gegensatz zu nur zwei oder drei Mal pro Sekunde (wie Sie möchten).
Die meiste Zeit starte ich meine Browser mit JavaScript aus diesem Grund. Aber ich verwende Yosemite 10.10.3 und ich denke, es gibt eine Art Timer-Problem damit - zumindest auf meinem alten System (relativ alt - dh 2011).
quelle