Signatur
Beschreibung
requestAnimationFrame() teilt dem Browser mit, dass Sie eine Animation ausführen möchten. Er ruft die übergebene Funktion genau einmal auf, und zwar direkt bevor er das nächste Bild zeichnet. Wie oft das passiert, hängt von der Bildwiederholrate des Bildschirms ab: meist 60 Hz, auf 120-Hz- oder 144-Hz-Bildschirmen entsprechend öfter.
Im Vergleich zu setTimeout() oder setInterval() hat die Methode mehrere Vorteile:
- Die Aktualisierungen sind an das Neuzeichnen gekoppelt. So gibt es kein Ruckeln und keine überzähligen Bilder.
- In Hintergrund-Tabs und verborgenen
<iframe>s pausiert der Browser die Aufrufe oder bremst sie stark. Das spart Akku und Rechenzeit. - Der Callback erhält einen hochauflösenden Zeitstempel vom Typ
DOMHighResTimeStamp. Damit lassen sich Animationen zeitbasiert statt bildbasiert rechnen.
Ein Aufruf plant nur einen einzigen Callback ein. Für eine fortlaufende Animation ruft der Callback requestAnimationFrame() daher selbst erneut auf; man spricht von einer „Render-Schleife“. Mit der zurückgegebenen ID lässt sich ein geplanter Aufruf über cancelAnimationFrame() wieder abbrechen.
Typische Einsatzfälle sind Canvas- und WebGL-Rendering, Spiele-Schleifen, eigene Scroll- und Übergangseffekte und das Bündeln von DOM-Messungen und DOM-Änderungen auf einen Zeitpunkt pro Bild. Einfache Übergänge lassen sich oft besser mit CSS-Transitions oder der Web Animations API umsetzen.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $callback Pflicht | (timestamp: DOMHighResTimeStamp) => void | Funktion, die vor dem nächsten Neuzeichnen aufgerufen wird. Sie erhält als einziges Argument einen Zeitstempel in Millisekunden (vergleichbar mit performance.now()). Er gibt den Beginn des aktuellen Bildes an. Alle Callbacks eines Bildes erhalten denselben Wert. |
Rückgabewert
cancelAnimationFrame(id) lässt sich der Aufruf damit abbrechen.Beispiele
Zeitbasierte Render-Schleife (unabhängig von der Bildwiederholrate)
const box = document.querySelector('#box');
const speed = 200; // Pixel pro Sekunde
let x = 0;
let lastTime = null;
const step = (timestamp) => {
if (lastTime === null) lastTime = timestamp;
const delta = (timestamp - lastTime) / 1000; // Sekunden seit dem letzten Bild
lastTime = timestamp;
x = (x + speed * delta) % window.innerWidth;
box.style.transform = `translateX(${x}px)`;
requestAnimationFrame(step); // nächstes Bild einplanen
};
requestAnimationFrame(step);
Animation starten und stoppen mit cancelAnimationFrame
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
let frameId = null;
let angle = 0;
const draw = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(angle);
ctx.fillRect(-25, -25, 50, 50);
ctx.restore();
angle += 0.02;
frameId = requestAnimationFrame(draw);
};
document.querySelector('#start').addEventListener('click', () => {
if (frameId === null) frameId = requestAnimationFrame(draw);
});
document.querySelector('#stop').addEventListener('click', () => {
cancelAnimationFrame(frameId);
frameId = null;
console.log('Animation gestoppt');
});
Scroll-Handler drosseln: höchstens eine DOM-Aktualisierung pro Bild
const progress = document.querySelector('#progress');
let ticking = false;
window.addEventListener('scroll', () => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
const max = document.documentElement.scrollHeight - window.innerHeight;
const percent = max > 0 ? (window.scrollY / max) * 100 : 0;
progress.style.width = `${percent}%`;
ticking = false;
});
}, { passive: true });
Auf das nächste Bild warten (Promise-Hilfsfunktion)
const nextFrame = () => new Promise((resolve) => requestAnimationFrame(resolve));
const fadeIn = async (el) => {
el.style.opacity = '0';
el.hidden = false;
await nextFrame(); // Browser übernimmt den Ausgangszustand
el.style.transition = 'opacity 300ms';
el.style.opacity = '1';
const t = await nextFrame();
console.log('Übergang gestartet bei', Math.round(t), 'ms');
};
fadeIn(document.querySelector('#dialog'));
// Wichtig · Fallstricke
- Kein Dauer-Timer: Jeder Aufruf löst den Callback nur einmal aus. Für eine fortlaufende Animation muss sich der Callback selbst neu einplanen.
- Bildwiederholrate schwankt: Rechnen Sie Bewegungen immer mit dem Zeitunterschied (
timestamp - lastTime) statt mit festen Schritten pro Bild. Sonst läuft die Animation auf einem 120-Hz-Bildschirm doppelt so schnell wie auf einem 60-Hz-Bildschirm. - Hintergrund-Tabs: In verborgenen Tabs pausiert der Browser die Callbacks oder bremst sie stark. Wenn der Nutzer zurückkehrt, kann der Zeitunterschied daher sehr groß sein. Begrenzen Sie ihn, zum Beispiel mit
Math.min(delta, 0.1), damit Objekte nicht „springen“. - Mehrfaches Einplanen: Wenn Sie
requestAnimationFrame()ohne Schutz in Event-Handlern aufrufen, können sich mehrere parallele Schleifen bilden. Merken Sie sich deshalb die ID oder nutzen Sie ein Flag. - Leistung: Rechenintensive Callbacks (mehr als etwa 16 ms bei 60 Hz) führen zu ausgelassenen Bildern. Wenn Sie im selben Bild abwechselnd DOM-Werte lesen (
offsetWidth) und schreiben, erzwingen Sie wiederholte Layout-Berechnungen (Layout-Thrashing). Lesen Sie daher zuerst alle Werte und schreiben Sie danach. - Kompatibilität: Alle aktuellen Browser unterstützen die Methode ohne Präfix. Präfixe wie
webkitRequestAnimationFramesind veraltet. In Web Workern steht sie nur in Verbindung mitOffscreenCanvaszur Verfügung (self.requestAnimationFrame). In Node.js gibt es sie nicht. - Innerhalb des Callbacks ist
thisnicht an ein Objekt gebunden. Verwenden Sie Pfeilfunktionen oderbind(), wenn Sie Methoden als Callback übergeben.