MWCodebymw.de ↗
JavaScript

requestAnimationFrame – die Animationsschleife, die zum Bildschirm passt

setInterval(schritt, 16) läuft auf einem 144-Hz-Monitor anders als auf einem alten Handy – und im Hintergrundtab einfach weiter. requestAnimationFrame löst beides, wenn man die verstrichene Zeit mitrechnet statt Bilder zu zählen.

Der naheliegende Weg für eine Bewegung im Browser:

// ⚠️ zwei Probleme auf einmal
setInterval(() => {
    x += 2;
    zeichne();
}, 16);

Problem eins: 16 ist geraten. Auf einem 60-Hz-Bildschirm passt es ungefähr, auf einem 120-Hz-Gerät ruckelt es, weil deine Schritte nicht zum Bildaufbau passen. Problem zwei: Das läuft weiter, wenn der Tab im Hintergrund liegt — und frisst Akku für Bilder, die niemand sieht.

Die richtige Schleife

let letzte = 0;

function schritt(jetzt) {
    const dt = letzte ? (jetzt - letzte) / 1000 : 0;   // Sekunden seit dem letzten Bild
    letzte = jetzt;

    aktualisiere(dt);
    zeichne();

    requestAnimationFrame(schritt);
}

requestAnimationFrame(schritt);

Der Browser ruft schritt genau dann auf, wenn er ohnehin ein Bild zeichnet — passend zur Bildwiederholrate des Geräts. Und im Hintergrundtab pausiert er von selbst.

Der eigentliche Kern: in Sekunden rechnen, nicht in Bildern

// ⚠️ hängt von der Bildrate ab
x += 2;

// ✅ überall gleich schnell
x += 200 * dt;      // 200 Pixel pro Sekunde

Das ist der Unterschied zwischen „läuft bei mir gut" und „läuft überall gleich". Mit dt bewegt sich dein Objekt auf einem 144-Hz-Monitor genauso schnell wie auf einem Fünf-Jahre-alten Handy — es wird dort nur flüssiger dargestellt.

Der Sprung nach dem Tab-Wechsel

Kommt der Nutzer nach drei Minuten zurück, ist dt plötzlich riesig, und alles springt auf einen Schlag durch die halbe Szene. Deshalb deckle ich den Wert immer:

const dt = Math.min((jetzt - letzte) / 1000, 0.05);   // höchstens 50 ms

Lieber eine kurze Zeitlupe als ein Objekt, das durch eine Wand teleportiert.

Sauber anhalten

requestAnimationFrame gibt eine ID zurück, mit der sich die Schleife stoppen lässt:

let id = null;

function start() { if (!id) id = requestAnimationFrame(schritt); }
function stopp() { cancelAnimationFrame(id); id = null; letzte = 0; }

Das letzte = 0 beim Stoppen ist wichtig: Beim nächsten Start wäre dt sonst die gesamte Pause.

Wann ich es *nicht* benutze

Für reine Gestaltungseffekte — Einblenden, Hover, Aufklappen — nehme ich CSS-Transitions und -Animationen. Die laufen im Browser auf einer eigenen Ebene, oft ganz ohne den Haupt-Thread, und sind praktisch immer flüssiger als alles, was ich in JavaScript nachbaue.

requestAnimationFrame ist für das, was CSS nicht kann: Spiellogik, Canvas, Physik, alles, wo der nächste Zustand vom vorherigen abhängt. In meinen Browserspielen ist genau das die Hauptschleife — und die dt-Rechnung der Grund, warum sie auf dem Handy und am Rechner gleich schnell laufen.

Du hast eine Animation, die auf manchen Geräten viel zu schnell ist? Das ist fast immer eine fehlende dt-Rechnung. Schreib mir.

Quellen

#JavaScript#Animation#requestAnimationFrame#Canvas#Performance

Du brauchst mehr als ein Snippet?

Ich entwickle Android-Apps in Kotlin und moderne Websites für Selbstständige und kleine Unternehmen — von der ersten Idee bis zum Release.

Projekt anfragen →