Start · Sprachen · JavaScript · Referenz · Window.requestAnimationFrame()

Window.requestAnimationFrame()

Methode

Plant einen Callback ein, den der Browser vor dem nächsten Neuzeichnen aufruft. Das ist die Grundlage für flüssige, bildschirmsynchrone Animationen.

seit JavaScript Baseline: alle Browser (seit 2012/2015) Kategorie: webapi

Signatur

Window.requestAnimationFrame(callback)

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

Typ
number
Beschreibung
Eine ganzzahlige ID ungleich 0, die den geplanten Callback eindeutig kennzeichnet. Über 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');
});
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'));
Übergang gestartet bei 1234 ms

// 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 webkitRequestAnimationFrame sind veraltet. In Web Workern steht sie nur in Verbindung mit OffscreenCanvas zur Verfügung (self.requestAnimationFrame). In Node.js gibt es sie nicht.
  • Innerhalb des Callbacks ist this nicht an ein Objekt gebunden. Verwenden Sie Pfeilfunktionen oder bind(), wenn Sie Methoden als Callback übergeben.