Frage zum Wiki-Artikel „requestAnimationFrame“
Thomas Mell
- frage zum wiki
- javascript
Die Aussage "Sie können keine eigenen Parameter für diese Funktion bereitstellen." ist falsch.
requestAnimationFrame(function () { myFunction(para1, para2); }
oder
requestAnimationFrame(() => { myFunction(para1, para2); }
Lieber Thomas,
Die Aussage "Sie können keine eigenen Parameter für diese Funktion bereitstellen." ist falsch.
ist sie das? Ich beweise Dir, dass sie nicht falsch ist.
Auf der fraglichen Seite wird davon gesprochen, dass die Methode requestAnimationFrame
des window
-Objektes ein Funktionsobjekt als Argument benötigt. Dieses ist der einzige Parameter, den diese Methode auswertet, weitere Parameter können zwar an diese Methode übergeben werden, jedoch werden sie nicht ausgewertet werden. Und genau das beweist Du im Grunde auch mit Deinen eigenen beiden Code-Beispielen:
requestAnimationFrame(function () { myFunction(para1, para2); }
oder
requestAnimationFrame(() => { myFunction(para1, para2); }
In beiden Beispielen übergibst Du genau ein Argument an requestAnimationFrame
, nämlich ein Funktionsobjekt in Form einer anonymen Funktion. Das entspricht zu 100% der Aussage, von der Du behauptest, sie sei aber falsch. Wenn sie falsch wäre, dann könnte man etwas in dieser Art tun:
requestAnimationFrame(
(timeStamp, x) => myFunction("noch was", x, timeStamp),
42,
"Unsinn"
);
In meinem Beispiel werden drei Argumente an requestAnimationFrame
übergeben:
Das zweite und dritte Argument wird von der Methode requestAnimationFrame
ignoriert werden. Genau das ist der Hintergrund der von Dir als falsch bezeichneten Aussage. Die Methode requestAnimationFrame
ist so spezifiziert, dass sie eben nur das Funktionsobjekt nimmt, und keine weiteren Argumente auswertet.
Schauen wir uns einmal die Parameter des übergebenen Funktionsobjektes an. Die Variable timeStamp
wird beim ersten Frame den Wert undefined
erhalten, bei einem späteren Frame dagegen eine Ganzzahl, welche die Millisekunden abbilden wird, die seit dem Beginn der Animation vergangen sind. Da ein browserinterner Mechanismus unsere Funktion aufrufen wird, wird er steuern, wieviele Argumente er an unsere Funktion übergeben wird, und mit welchen Werten als Inhalt. Das bedeutet, dass der zweite Parameter x
niemals etwas anderes als undefined
sein wird, weil der Mechanismus hinter requestAnimationFrame
so spezifiziert ist, dass er eben nur diesen einen Wert an unsere Funktion übergeben wird.
Vermutlich sitzt Du einem Missverständnis auf: Deine beiden anonymen Funktionen nehmen beide überhaupt gar kein Argument entgegen, denn die runden Klammern sind bei beiden leer! Dass innerhalb Deiner anonymen Funktionen dann eine ganz andere Funktion namens myFunction
aufgerufen wird, die dann auch noch zwei Parameter gefüttert bekommt, ist etwas völlig anderes und hat mit requestAnimationFrame
selbst nichts mehr zu tun. Es ist in Deinen Beispielen auch ungeklärt, woher die Variablen para1
und para2
stammen, oder welche Werte sie haben sollen, und wie diese Werte da hinein gekommen sein sollen. Aber das ist wie schon gesagt völlig unabhängig davon, wie requestAnimationFrame
selbst funktioniert.
Die Aussage im Artikel ist also sachlich korrekt.
Liebe Grüße
Felix Riesterer
Hallo Thomas,
Felix hat mich überholt, ich schreib's aber trotzdem. Ey Felix, du bist krank, schon Dich 😉
Du kannst der anonymen Funktion, die Du jetzt an requestAnimationFrame übergibst, weiterhin keine Parameter mitgeben. Du hast lediglich einen - potenziell fehleranfälligen und ressourcenhungrigen - Workaround gebaut. Und falsch noch dazu.
Denn: durch deine anonyme Funktion erzeugst Du eine Closure, die den Kontext einschließt, der im Moment der Erzeugung der anonymen Funktion gilt. Ich erweitere deine Lösung mal etwas, damit man sieht, wo parm1 und parm2 herkommen könnten:
function doAnimation() {
let parm1 = document.querySelectorAll(".foo"),
parm2 = 42;
requestAnimationFrame(function() { myFunction(parm1, parm2); });
parm2 = 19;
requestAnimationFrame(function() { otherFunction(parm1, parm2); });
parm2 = 91;
}
Ja, ich weiß, Bernhard Hoëcker und Wigald Boning wären begeistert. Ein Musterbeispiel von „Nicht nachmachen“.
Ressourcenleck: Ich habe jetzt nur 2 Variablen verwendet, aber stell Dir vor, doAnimation hätte 20 davon. All diese Variablen würden Teil der Closure werden, die das Erzeugen der anonymen Funktion bildet, und diese Closure hängt dann zusammen mit der Funktion in der Animationswarteschlange. Zugegeben, da bleibt sie nicht lange, aber Daten, die länger leben als nötig, sind mit Vorsicht zu betrachten.
Fehleranfällig: Der Animationszyklus läuft außerhalb der normalen JavaScript-Verarbeitung, d.h. wenn myFunction oder otherFunction aufgerufen wird, hat parm2 NICHT den Wert, den es beim Registrieren der Funktion hatte, sondern den Wert, der ihm zuletzt zugewiesen wurde. Also 91.
Falsch: Die Callbackfunktion von requestAnimationFrame erhält einen timestamp-Parameter. Den muss dein Workaround unbedingt an myFunction durchreichen!
Im Artikel habe ich beschrieben, dass sich die Callback-Funktion vor allem um das Interfacing mit requestAnimationFrame kümmern sollte. Also die vergangene Zeit berechnen, und vor allem einen neuen Animationframe bestellen. Sowas schreibt man dann nur einmal. Im Wiki habe ich das mit globalen Variablen gemacht, aber man könnte hier auch eine Klasse nutzen.
class Animator {
#callback;
#renderer;
#startZeit = undefined;
#vorigeZeit;
#requestId = undefined;
constructor(renderer) {
this.#callback = this.#animate.bind(this);
this.#renderer = renderer;
}
run() {
this.#requestId = requestAnimationFrame(this.#callback);
}
stop() {
if (this.#requestId)
cancelAnimationFrame(this.#requestId);
}
#animate(timestamp) {
if (this.#startZeit === undefined) {
this.#startZeit = timestamp;
this.#vorigeZeit = timestamp;
}
this.#renderer(timestamp - this.#startZeit, timestamp - this.#vorigeZeit);
this.#vorigeZeit = timestamp;
this.run();
}
}
const a = new Animator(myFunction);
a.run();
...
a.stop();
Immer noch keine eigenen Parameter? Nein, immer noch nicht. Das ist etwas, worum sich der VERWENDER der Klasse kümmern kann, nicht die Klasse. Denn diese Parameter könnten ja auch etwas sein, was sich ändern können muss. Und dann wäre doch wieder ein Objekt ideal:
const anim = {
foo: document.getElementById("foo"),
status: 12,
animate(animTime, diffTime) {
...
this.status = 99;
}
}
const b = new Animator(anim.animate.bind(anim));
Durch bind ist der Callback, den der Animator bekommt, nun an das anim-Objekt gebunden, d.h. wenn der Animator die render-Funktion ruft, findet sich anim.animate automatisch im Kontext von anim wieder und kann es als this benutzen. Und hat nun alle Daten, die es braucht und kann sich sogar darin etwas merken, wenn's not tut.
Achso, ist alles ungetestet vom Hirn ins Forum gedonnert. Das ist aber was, was man mal im Wiki als Beispiel mit einbauen könnte. Wenn ich einmal Zeit hätt, dideldideldum.
Rolf