CSS und JavaScript-Animationsleistung
Animationen sind entscheidend für eine angenehme Benutzererfahrung in vielen Anwendungen. Es gibt viele Möglichkeiten, Webanimationen zu implementieren, wie etwa CSS-Übergänge/Animationen oder JavaScript-basierte Animationen (unter Verwendung von requestAnimationFrame()). In diesem Artikel analysieren wir die Leistungsunterschiede zwischen CSS-basierten und JavaScript-basierten Animationen.
CSS-Übergänge und -Animationen
Sowohl CSS-Übergänge als auch Animationen können zum Schreiben von Animationen verwendet werden. Sie haben jeweils ihre eigenen Anwendungsszenarien:
- CSS-
Übergängebieten eine einfache Möglichkeit, Animationen zwischen dem aktuellen Stil und einem End-CSS-Zustand zu erstellen, z. B. ein ruhender Button-Zustand und ein Hover-Zustand. Auch wenn sich ein Element in der Mitte eines Übergangs befindet, beginnt der neue Übergang sofort aus dem aktuellen Stil, anstatt zum End-CSS-Zustand zu springen. Weitere Details finden Sie unter Using CSS transitions. - CSS-
Animationenhingegen erlauben es Entwicklern, Animationen zwischen einer Reihe von Anfangseigenschaftswerten und einem finalen Satz zu erstellen, anstatt zwischen zwei Zuständen. CSS-Animationen bestehen aus zwei Komponenten: einem Stil, der die CSS-Animation beschreibt, und einer Reihe von Keyframes, die die Start- und Endzustände des Animationsstils sowie mögliche Zwischenpunkte angeben. Weitere Details finden Sie unter Using CSS animations.
In Bezug auf die Leistung gibt es keinen Unterschied zwischen der Implementierung einer Animation mit CSS-Übergängen oder Animationen. Beide werden in diesem Artikel unter dem gleichen CSS-basierten Oberbegriff klassifiziert.
requestAnimationFrame
Die requestAnimationFrame() API bietet eine effiziente Möglichkeit, Animationen in JavaScript zu erstellen. Die Callback-Funktion der Methode wird vom Browser vor dem nächsten Neuzeichnen bei jedem Frame aufgerufen. Im Vergleich zu setTimeout()/setInterval(), die einen spezifischen Verzögerungsparameter benötigen, ist requestAnimationFrame() wesentlich effizienter. Entwickler können eine Animation erstellen, indem sie den Stil eines Elements jedes Mal ändern, wenn die Schleife aufgerufen wird (oder das Canvas-Draw aktualisieren, oder was auch immer).
Hinweis:
Wie CSS-Übergänge und -Animationen pausiert requestAnimationFrame(), wenn der aktuelle Tab in den Hintergrund geschoben wird.
Weitere Details finden Sie in animating with JavaScript from setInterval to requestAnimationFrame.
Leistungsvergleich:
Übergänge vs. requestAnimationFrame
Tatsache ist, dass die Leistung von CSS-basierten Animationen in den meisten Fällen nahezu identisch ist mit JavaScript-Animationen – zumindest in Firefox. Einige JavaScript-basierte Animationsbibliotheken, wie GSAP und Velocity.JS, behaupten sogar, dass sie besser abschneiden als native CSS-Übergänge/-Animationen. Dies kann passieren, weil CSS-Übergänge/-Animationen vor jedem Neuzeichnen die Stile der Elemente im Haupt-UI-Thread neu abtasten, was fast dasselbe ist wie das Neusamplen von Elementeigenschaften via einem requestAnimationFrame()-Callback, das ebenfalls vor dem nächsten Neuzeichnen ausgelöst wird. Wenn beide Animationen im Haupt-UI-Thread erstellt werden, gibt es leistungsmäßig keinen Unterschied.
In diesem Abschnitt führen wir Sie durch einen Leistungstest, der in Firefox durchgeführt wird, um zu sehen, welche Animationsmethode insgesamt besser abschneidet.
Aktivieren von FPS-Werkzeugen
Bevor Sie das Beispiel durchgehen, aktivieren Sie bitte zuerst die FPS-Werkzeuge, um die aktuelle Bildrate zu sehen:
- Geben Sie in der URL-Leiste about:config ein; klicken Sie auf die Schaltfläche
Ich werde vorsichtig sein, ich verspreche es!, um in den Konfigurationsbildschirm zu gelangen.
- Suchen Sie in der Suchleiste nach der Einstellung
layers.acceleration.draw-fps. - Doppelklicken Sie auf den Eintrag, um den Wert auf
truezu setzen. Jetzt sehen Sie drei kleine lila Kästchen in der oberen linken Ecke des Firefox-Fensters. Das erste Kästchen repräsentiert FPS.
Ausführen des Leistungstests
Zu Beginn des Tests, der unten zu sehen ist, werden insgesamt 1000 <div>-Elemente durch CSS-Animation transformiert.
const boxes = [];
const button = document.getElementById("toggle-button");
const boxContainer = document.getElementById("box-container");
const animationType = document.getElementById("type");
// create boxes
for (let i = 0; i < 1000; i++) {
const div = document.createElement("div");
div.classList.add("css-animation");
div.classList.add("box");
boxContainer.appendChild(div);
boxes.push(div.style);
}
let toggleStatus = true;
let rafId;
button.addEventListener("click", () => {
if (toggleStatus) {
animationType.textContent = " requestAnimationFrame";
for (const child of boxContainer.children) {
child.classList.remove("css-animation");
}
rafId = window.requestAnimationFrame(animate);
} else {
window.cancelAnimationFrame(rafId);
animationType.textContent = " CSS animation";
for (const child of boxContainer.children) {
child.classList.add("css-animation");
}
}
toggleStatus = !toggleStatus;
});
const duration = 6000;
const translateX = 500;
const rotate = 360;
const scale = 1.4 - 0.6;
let start;
function animate(time) {
if (!start) {
start = time;
rafId = window.requestAnimationFrame(animate);
return;
}
const progress = (time - start) / duration;
if (progress < 2) {
let x = progress * translateX;
let transform;
if (progress >= 1) {
x = (2 - progress) * translateX;
transform = `translateX(${x}px) rotate(${
(2 - progress) * rotate
}deg) scale(${0.6 + (2 - progress) * scale})`;
} else {
transform = `translateX(${x}px) rotate(${progress * rotate}deg) scale(${
0.6 + progress * scale
})`;
}
for (const box of boxes) {
box.transform = transform;
}
} else {
start = null;
}
rafId = window.requestAnimationFrame(animate);
}
Die Animation kann durch Klicken auf den Umschaltbutton auf requestAnimationFrame() gewechselt werden.
Versuchen Sie, beide jetzt auszuführen und vergleichen Sie die FPS für jede (das erste lila Feld). Sie sollten sehen, dass die Leistung von CSS-Animationen und requestAnimationFrame() sehr nah beieinander liegt.
Animation außerhalb des Haupt-Threads
Trotz der oben genannten Testergebnisse würden wir argumentieren, dass CSS-Animationen die bessere Wahl sind. Aber wie? Der Schlüssel liegt darin, dass solange die Eigenschaften, die wir animieren wollen, keinen Neulayout/Neuzeichnen auslösen (lesen Sie CSS triggers für mehr Informationen), können wir diese Sampling-Operationen aus dem Haupt-Thread heraus verschieben. Die häufigste Eigenschaft ist die CSS-Transformation. Wenn ein Element als Layer promotet wird, können Transformationseigenschaften auf der GPU animiert werden, was eine bessere Leistung/Effizienz bedeutet, insbesondere auf mobilen Geräten. Finden Sie mehr Details heraus in OffMainThreadCompositing.
Um die OMTA (Off Main Thread Animation) in Firefox zu aktivieren, können Sie about:config aufrufen und nach layers.offmainthreadcomposition.async-animations suchen. Setzen Sie seinen Wert auf true.

Nachdem Sie OMTA aktiviert haben, versuchen Sie, den oben genannten Test erneut auszuführen. Sie sollten sehen, dass die FPS der CSS-Animationen nun deutlich höher sind.
Hinweis: In Nightly/Developer Edition sollten Sie sehen, dass OMTA standardmäßig aktiviert ist, sodass Sie die Tests möglicherweise in umgekehrter Reihenfolge durchführen müssen (zuerst mit aktivierter OMTA testen, dann deaktivieren, um ohne OMTA zu testen).
Zusammenfassung
Browser sind in der Lage, Rendering-Flows zu optimieren. Zusammenfassend sollten wir immer versuchen, unsere Animationen mit CSS-Übergängen/-Animationen zu erstellen, wo dies möglich ist. Wenn Ihre Animationen wirklich komplex sind, müssen Sie möglicherweise auf JavaScript-basierte Animationen zurückgreifen.