Kurs HTML i CSS · Moduł 6: Animacje CSS

Wydajność animacji CSS - optymalizacja i GPU acceleration

11 min czytania ·
W tej lekcji13

Witaj w Warsztacie Budowniczych Piramid! Podobnie jak starożytni Egipcjanie musieli budować piramidy efektywnie, aby oszczędzać zasoby i czas, tak i my musimy tworzyć wydajne animacje, które działają płynnie nawet na słabszych urządzeniach. Poznaj sekrety GPU acceleration, will-change i jak unikać wolnych animacji!

Dlaczego wydajność animacji ma znaczenie?

Problem: Animacje mogą powodować:

  • Spadek FPS (frames per second) poniżej 60 FPS
  • Lagowanie i "stuttering"
  • Wyczerpywanie baterii na urządzeniach mobilnych
  • Złe wrażenia użytkownika (UX)

Cel: Płynne 60 FPS = 16.67ms na klatkę

Analogia egipska: Jak budowniczowie piramid musieli używać odpowiednich technik (dźwignie, rampy), aby unosić ciężkie bloki, tak i my musimy używać odpowiednich właściwości CSS, aby animacje były lekkie i szybkie.

GPU vs CPU - zrozumienie renderowania

CPU (Central Processing Unit)

  • Procesor główny komputera
  • Ogólnego przeznaczenia
  • Wolniejszy dla grafiki

GPU (Graphics Processing Unit)

  • Procesor graficzny
  • Zoptymalizowany do obliczeń graficznych
  • Znacznie szybszy dla animacji i transformacji

Kluczowa różnica:

  • CPU: Sekwencyjne przetwarzanie
  • GPU: Równoległe przetwarzanie tysięcy pikseli jednocześnie

Właściwości GPU-accelerated (SZYBKIE) vs CPU-bound (WOLNE)

GPU-accelerated (ZAWSZE używaj do animacji)

/* Transform - wszystkie funkcje */
.fast {
  transform: translate(100px, 50px);    /* GPU */
  transform: scale(1.5);                /* GPU */
  transform: rotate(45deg);             /* GPU */
  transform: skew(10deg);               /* GPU */
  transform: translateZ(0);             /* GPU */
  transform: translate3d(10px, 20px, 0);/* GPU */
}

/* Opacity */
.fast {
  opacity: 0.5;                         /* GPU */
}

Dlaczego są szybkie:

  • Nie powodują reflow (przeliczenia layoutu)
  • Nie powodują repaint (przemalowania)
  • GPU renderuje je w osobnej warstwie (compositing layer)

CPU-bound (UNIKAJ w animacjach)

/* Właściwości zmieniające layout */
.slow {
  width: 300px;        /* CPU - reflow całej strony */
  height: 200px;       /* CPU - reflow całej strony */
  margin: 20px;        /* CPU - reflow całej strony */
  padding: 10px;       /* CPU - reflow całej strony */
  top: 100px;          /* CPU - reflow elementu */
  left: 50px;          /* CPU - reflow elementu */
  font-size: 20px;     /* CPU - reflow tekstu */
}

/* Właściwości zmieniające wygląd */
.slow {
  background: red;     /* CPU - repaint */
  color: blue;         /* CPU - repaint */
  border: 1px solid;   /* CPU - repaint */
  box-shadow: 0 0 10px;/* CPU - repaint (ciężki!) */
}

Dlaczego są wolne:

  • Reflow = przeglądarka musi przeliczyć pozycje WSZYSTKICH elementów
  • Repaint = przeglądarka musi przemalować piksele
  • Blokują główny wątek (main thread)

Praktyczne przykłady - WOLNE vs SZYBKIE

Przykład 1: Przesuwanie elementu

/* WOLNE - animacja left */
.box-slow {
  position: relative;
  left: 0;
  transition: left 0.3s;
}

.box-slow:hover {
  left: 100px;  /* Powoduje reflow przy każdej klatce! */
}

/* SZYBKIE - animacja transform */
.box-fast {
  transform: translateX(0);
  transition: transform 0.3s;
}

.box-fast:hover {
  transform: translateX(100px);  /* GPU-accelerated! */
}

Wynik:

  • Wolne: ~30-40 FPS (widoczne lagowanie)
  • Szybkie: 60 FPS (płynnie)

Przykład 2: Powiększanie elementu

/* WOLNE - animacja width/height */
.card-slow {
  width: 200px;
  height: 300px;
  transition: width 0.3s, height 0.3s;
}

.card-slow:hover {
  width: 220px;    /* Reflow! */
  height: 330px;   /* Reflow! */
}

/* SZYBKIE - animacja scale */
.card-fast {
  width: 200px;
  height: 300px;
  transform: scale(1);
  transition: transform 0.3s;
}

.card-fast:hover {
  transform: scale(1.1);  /* GPU! 220px × 330px */
}

Przykład 3: Fade in/out

/* WOLNE - animacja visibility */
.modal-slow {
  visibility: visible;
  transition: visibility 0.3s;
}

.modal-slow.hidden {
  visibility: hidden;  /* Nie można animować! Skok! */
}

/* SZYBKIE - animacja opacity */
.modal-fast {
  opacity: 1;
  transition: opacity 0.3s;
}

.modal-fast.hidden {
  opacity: 0;  /* Płynna animacja GPU! */
  pointer-events: none;  /* Wyłącz interakcje */
}

Przykład 4: Box shadow (bardzo kosztowne!)

/* BARDZO WOLNE - animacja box-shadow */
.button-slow {
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  transition: box-shadow 0.3s;
}

.button-slow:hover {
  box-shadow: 0 8px 16px rgba(0,0,0,0.3);  /* Bardzo ciężkie! */
}

/* SZYBSZE - pseudo-element z opacity */
.button-fast {
  position: relative;
}

.button-fast::after {
  content: '';
  position: absolute;
  inset: 0;
  box-shadow: 0 8px 16px rgba(0,0,0,0.3);
  opacity: 0;
  transition: opacity 0.3s;
  pointer-events: none;
}

.button-fast:hover::after {
  opacity: 1;  /* Animuj tylko opacity! */
}

will-change - powiadomienie przeglądarki o przyszłych zmianach

will-change to hint dla przeglądarki: "Hej, za chwilę będę animować tę właściwość, przygotuj się!"

Jak działa will-change

.element {
  will-change: transform;
  /* Przeglądarka tworzy GPU layer PRZED animacją */
}

.element:hover {
  transform: scale(1.2);  /* Już gotowe! */
}

Co się dzieje:

  1. Przeglądarka tworzy osobną warstwę (compositing layer) dla elementu
  2. Element jest renderowany przez GPU
  3. Animacja jest natychmiastowa i płynna

Kiedy używać will-change

/* DOBRE - dla elementów często animowanych */
.menu-item {
  will-change: transform;
  transition: transform 0.2s;
}

.menu-item:hover {
  transform: translateY(-5px);
}

/* DOBRE - dla animacji keyframes */
.spinner {
  will-change: transform;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* DOBRE - dodaj dynamicznie przed animacją */
.modal {
  /* Brak will-change domyślnie */
}

.modal.opening {
  will-change: transform, opacity;  /* Dodaj przed animacją */
}

.modal.open {
  will-change: auto;  /* Usuń po animacji! */
}

Kiedy NIE używać will-change

/* ZŁE - na wszystkich elementach */
* {
  will-change: transform;  /* Zużywa MASĘ pamięci! */
}

/* ZŁE - na statycznych elementach */
.logo {
  will-change: transform;  /* Logo się nie rusza! */
}

/* ZŁE - zbyt wiele właściwości */
.element {
  will-change: transform, opacity, width, height, background, color;
  /* Zbyt dużo - przeciwskuteczne! */
}

/* ZŁE - zostawione na stałe */
.button {
  will-change: transform;  /* Zostawione na zawsze = leak pamięci */
}

Zasady will-change:

  1. Tylko dla animowanych elementów
  2. Tylko 1-2 właściwości jednocześnie
  3. Dodaj przed animacją, usuń po animacji
  4. Testuj zużycie pamięci - może być kontrproduktywne!

Dynamiczne dodawanie will-change (JavaScript)

const element = document.querySelector('.box');

// Dodaj przed animacją (np. na mouseenter)
element.addEventListener('mouseenter', () => {
  element.style.willChange = 'transform';
});

// Usuń po animacji (na transitionend)
element.addEventListener('transitionend', () => {
  element.style.willChange = 'auto';
});

Force GPU acceleration - trick translateZ(0)

Jeśli przeglądarka nie tworzy GPU layer automatycznie, możesz wymusić to:

.element {
  /* Force GPU layer */
  transform: translateZ(0);
  /* lub */
  transform: translate3d(0, 0, 0);
  /* lub */
  will-change: transform;
}

Przykład praktyczny:

/* Obrazek, który będzie zoomowany */
.image {
  transform: translateZ(0);  /* Force GPU */
  transition: transform 0.3s;
}

.image:hover {
  transform: scale(1.1);  /* Teraz gwarantowane GPU */
}

Uwaga: Nie używaj na wszystkich elementach - zużywa pamięć GPU!

Unikanie animacji width/height - layout thrashing

Problem: Animowanie width lub height powoduje reflow przy każdej klatce.

Wolny sposób - animacja width

.progress-bar-slow {
  width: 0%;
  transition: width 2s;
}

.progress-bar-slow.complete {
  width: 100%;  /* Reflow przy każdej klatce! */
}

Szybki sposób - transform: scaleX

.progress-bar-fast {
  width: 100%;  /* Pełna szerokość od razu */
  transform: scaleX(0);  /* Skala 0% */
  transform-origin: left;  /* Rośnij od lewej */
  transition: transform 2s;
}

.progress-bar-fast.complete {
  transform: scaleX(1);  /* Skala 100% - GPU! */
}

Alternatywa - clip-path

.progress-bar-clip {
  width: 100%;
  background: linear-gradient(to right, #3498db, #2ecc71);
  clip-path: inset(0 100% 0 0);  /* Ukryj prawą stronę */
  transition: clip-path 2s;
}

.progress-bar-clip.complete {
  clip-path: inset(0 0 0 0);  /* Pokaż wszystko */
}

requestAnimationFrame vs CSS Animations

CSS Animations - PREFEROWANE

Zalety:

  • Zoptymalizowane przez przeglądarkę
  • Działają w osobnym wątku (compositor thread)
  • Płynne nawet gdy main thread jest zajęty
  • Mniej kodu
  • Automatyczne GPU acceleration
/* CSS Animation - preferowane */
.box {
  animation: slide 1s ease-in-out infinite;
}

@keyframes slide {
  0% { transform: translateX(0); }
  100% { transform: translateX(100px); }
}

requestAnimationFrame (rAF) - dla złożonych animacji

Kiedy używać:

  • Animacje zależne od interakcji użytkownika (scroll, mouse)
  • Animacje z dynamicznymi wartościami (physics, easing)
  • Synchronizacja wielu animacji
  • Canvas/WebGL animacje
/* JavaScript rAF - dla złożonych przypadków */
let position = 0;
const box = document.querySelector('.box');

function animate() {
  position += 2;

  // Używaj transform, nie left!
  box.style.transform = `translateX(${position}px)`;

  if (position < 500) {
    requestAnimationFrame(animate);
  }
}

requestAnimationFrame(animate);

NIE używaj setInterval/setTimeout:

/* ZŁE - setInterval */
setInterval(() => {
  position += 2;
  box.style.left = position + 'px';  /* Nie zsynchronizowane z ekranem! */
}, 16);

/* DOBRE - requestAnimationFrame */
function animate() {
  position += 2;
  box.style.transform = `translateX(${position}px)`;
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

Performance monitoring - jak mierzyć wydajność

1. Chrome DevTools Performance Tab

Krok po kroku:

  1. Otwórz DevTools (F12)
  2. Zakładka Performance
  3. Kliknij Record (czerwone kółko)
  4. Wykonaj animację
  5. Kliknij Stop
  6. Analizuj:
    • FPS - czy 60 FPS?
    • CPU - czy main thread jest zajęty?
    • Rendering - ile czasu na reflow/repaint?

2. Rendering Tab - FPS Meter

DevTools → ⋮ (More tools) → Rendering → Frame Rendering Stats

Pokazuje:

  • FPS w czasie rzeczywistym
  • GPU memory usage
  • Paint flashing (czerwone prostokąty = repaint)

3. Layers Panel

DevTools → ⋮ (More tools) → Layers

Pokazuje:

  • Które elementy mają własną GPU layer
  • Dlaczego layer został stworzony
  • Ile pamięci zużywa

4. CSS Stats - will-change monitoring

/* Sprawdź ile elementów ma will-change */
*[style*="will-change"] {
  outline: 2px solid red !important;
}

Przykłady optymalizacji - przed i po

Optymalizacja 1: Card hover effect

/* PRZED - wolne (4 repaints, 2 reflows) */
.card-before {
  transition: all 0.3s;
}

.card-before:hover {
  width: 320px;         /* Reflow */
  box-shadow: 0 10px 30px rgba(0,0,0,0.3);  /* Repaint */
  border: 2px solid gold;  /* Repaint */
}

/* PO - szybkie (0 reflows, 1 composite) */
.card-after {
  position: relative;
  transition: transform 0.3s;
}

.card-after::before {
  content: '';
  position: absolute;
  inset: -2px;
  border: 2px solid gold;
  opacity: 0;
  transition: opacity 0.3s;
}

.card-after::after {
  content: '';
  position: absolute;
  inset: 0;
  box-shadow: 0 10px 30px rgba(0,0,0,0.3);
  opacity: 0;
  transition: opacity 0.3s;
}

.card-after:hover {
  transform: scale(1.05);  /* GPU */
}

.card-after:hover::before,
.card-after:hover::after {
  opacity: 1;  /* GPU */
}

Optymalizacja 2: Loading spinner

/* PRZED - wolne (animacja border) */
.spinner-before {
  border: 4px solid #f3f3f3;
  border-top: 4px solid #3498db;
  border-radius: 50%;
  animation: spin-slow 1s linear infinite;
}

@keyframes spin-slow {
  to {
    transform: rotate(360deg);
    border-top-color: #e74c3c;  /* Animacja koloru = repaint! */
  }
}

/* PO - szybkie (tylko transform) */
.spinner-after {
  border: 4px solid #f3f3f3;
  border-top: 4px solid #3498db;
  border-radius: 50%;
  will-change: transform;
  animation: spin-fast 1s linear infinite;
}

@keyframes spin-fast {
  to {
    transform: rotate(360deg);  /* Tylko GPU! */
  }
}

Optymalizacja 3: Smooth scroll reveal

/* PRZED - wolne (animacja top + opacity) */
.reveal-before {
  position: relative;
  top: 50px;
  opacity: 0;
  transition: top 0.6s, opacity 0.6s;
}

.reveal-before.visible {
  top: 0;       /* Reflow */
  opacity: 1;   /* GPU */
}

/* PO - szybkie (tylko transform + opacity) */
.reveal-after {
  transform: translateY(50px);
  opacity: 0;
  will-change: transform, opacity;
  transition: transform 0.6s, opacity 0.6s;
}

.reveal-after.visible {
  transform: translateY(0);  /* GPU */
  opacity: 1;                /* GPU */
}

Checklist wydajności animacji

Właściwości:

  • Animuję tylko transform i opacity
  • Unikam width, height, margin, padding
  • Unikam left, top, right, bottom
  • Unikam box-shadow (lub animuję przez pseudo-element)

GPU Acceleration:

  • Używam transform zamiast pozycjonowania
  • Używam scale zamiast width/height
  • Dodaję will-change dla często animowanych elementów
  • Usuwam will-change po animacji

Optymalizacja:

  • Preferuję CSS animations nad JavaScript
  • Jeśli JS - używam requestAnimationFrame, nie setInterval
  • Testuję w Chrome DevTools Performance
  • Sprawdzam FPS Meter (cel: 60 FPS)
  • Sprawdzam Layers Panel (memory usage)

Mobile:

  • Testuję na prawdziwych urządzeniach mobilnych
  • Ograniczam liczbę jednoczesnych animacji
  • Wyłączam animacje dla prefers-reduced-motion

Prefers-reduced-motion - dostępność

Niektórzy użytkownicy mają włączone ustawienie "Reduce motion" w systemie (zawroty głowy, epilepsja). Szanujmy to:

/* Normalne animacje */
.box {
  transition: transform 0.3s;
}

.box:hover {
  transform: scale(1.2) rotate(5deg);
}

/* Wyłącz dla użytkowników z prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
  .box {
    transition: none;  /* Brak animacji */
  }

  .box:hover {
    transform: scale(1.05);  /* Tylko subtelna zmiana */
  }
}

/* Lub globalnie */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Podsumowanie

GPU-accelerated (SZYBKIE):

  • transform (translate, scale, rotate, skew)
  • opacity
  • filter (blur, brightness - ale wolniejsze niż transform)

CPU-bound (WOLNE):

  • width, height, margin, padding
  • left, top, right, bottom
  • font-size, border-width
  • box-shadow (bardzo wolny!)
  • background-color, color (repaint)

will-change:

  • Używaj oszczędnie (1-2 właściwości)
  • Dodaj przed animacją, usuń po
  • Nie zostawiaj na stałe (memory leak!)

Performant patterns:

  • Używaj scale zamiast width/height
  • Używaj translateX/Y zamiast left/top
  • Używaj opacity zamiast visibility/display
  • Animuj cienie przez pseudo-elementy z opacity

Testing:

  • Chrome DevTools Performance Tab
  • FPS Meter (60 FPS = cel)
  • Layers Panel (GPU memory)
  • Testuj na mobile devices

Pamiętaj: Wydajne animacje to jak dobrze zorganizowana budowa piramid - używaj odpowiednich narzędzi (GPU = dźwignie) zamiast brutalnej siły (CPU = ręczne podnoszenie), a twoja strona będzie płynna jak piasek na pustyni!

Kod do tej lekcji: style.css
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: 'Segoe UI', sans-serif;
  background: linear-gradient(180deg, #ff6b35, #ff9a56, #ffd700);
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
}

.scene {
  text-align: center;
  padding: 20px;
}

h2 {
  margin-bottom: 30px;
  color: #3d2914;
}

.sun-container {
  position: relative;
  width: 300px;
  height: 300px;
  margin: 0 auto;
}

.sun {
  font-size: 80px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  /* Dodaj animacje pulseSun */
  /* animation: pulseSun 2s ease-in-out infinite; */
}

.ray {
  position: absolute;
  width: 4px;
  height: 60px;
  background: linear-gradient(180deg, #ffd700, transparent);
  top: 50%;
  left: 50%;
  transform-origin: center bottom;
  opacity: 0;
  /* Dodaj animacje rayGlow */
  /* animation: rayGlow 3s ease-in-out infinite; */
}

.ray-1 { transform: translate(-50%, -100%) rotate(0deg); }
.ray-2 { transform: translate(-50%, -100%) rotate(90deg); }
.ray-3 { transform: translate(-50%, -100%) rotate(180deg); }
.ray-4 { transform: translate(-50%, -100%) rotate(270deg); }

/* Dodaj rozne animation-delay */
/* .ray-1 { animation-delay: 0s; } */
/* .ray-2 { animation-delay: 0.5s; } */
/* .ray-3 { animation-delay: 1s; } */
/* .ray-4 { animation-delay: 1.5s; } */

/* Zdefiniuj @keyframes pulseSun */
/* @keyframes pulseSun { */
/*   0%, 100% { transform: translate(-50%, -50%) scale(1); } */
/*   50% { transform: translate(-50%, -50%) scale(1.2); } */
/* } */

/* Zdefiniuj @keyframes rayGlow */
/* @keyframes rayGlow { */
/*   0% { opacity: 0; height: 30px; } */
/*   50% { opacity: 1; height: 80px; } */
/*   100% { opacity: 0; height: 30px; } */
/* } */

.hint {
  margin-top: 30px;
  color: #3d2914;
  font-style: italic;
}

Hasła z dokumentacji w tej lekcji

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Które właściwości CSS są przyspieszane przez GPU i najlepsze do płynnych animacji?

  2. 2. Jaka liczba klatek na sekundę (FPS) oznacza płynną animację?

To 2 z 4 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Układanie w pionie

    Uporządkuj sposoby animowania od najwydajniejszego (GPU) do najwolniejszego (reflow):

Przydatne artykuły