Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Wydajność animacji CSS - optymalizacja i GPU acceleration

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)

1/* Transform - wszystkie funkcje */
2.fast {
3  transform: translate(100px, 50px);    /* ✅ GPU */
4  transform: scale(1.5);                /* ✅ GPU */
5  transform: rotate(45deg);             /* ✅ GPU */
6  transform: skew(10deg);               /* ✅ GPU */
7  transform: translateZ(0);             /* ✅ GPU */
8  transform: translate3d(10px, 20px, 0);/* ✅ GPU */
9}
10
11/* Opacity */
12.fast {
13  opacity: 0.5;                         /* ✅ GPU */
14}

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)

1/* Właściwości zmieniające layout */
2.slow {
3  width: 300px;        /* ❌ CPU - reflow całej strony */
4  height: 200px;       /* ❌ CPU - reflow całej strony */
5  margin: 20px;        /* ❌ CPU - reflow całej strony */
6  padding: 10px;       /* ❌ CPU - reflow całej strony */
7  top: 100px;          /* ❌ CPU - reflow elementu */
8  left: 50px;          /* ❌ CPU - reflow elementu */
9  font-size: 20px;     /* ❌ CPU - reflow tekstu */
10}
11
12/* Właściwości zmieniające wygląd */
13.slow {
14  background: red;     /* ❌ CPU - repaint */
15  color: blue;         /* ❌ CPU - repaint */
16  border: 1px solid;   /* ❌ CPU - repaint */
17  box-shadow: 0 0 10px;/* ❌ CPU - repaint (ciężki!) */
18}

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

1/* ❌ WOLNE - animacja left */
2.box-slow {
3  position: relative;
4  left: 0;
5  transition: left 0.3s;
6}
7
8.box-slow:hover {
9  left: 100px;  /* Powoduje reflow przy każdej klatce! */
10}
11
12/* ✅ SZYBKIE - animacja transform */
13.box-fast {
14  transform: translateX(0);
15  transition: transform 0.3s;
16}
17
18.box-fast:hover {
19  transform: translateX(100px);  /* GPU-accelerated! */
20}

Wynik:

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

Przykład 2: Powiększanie elementu

1/* ❌ WOLNE - animacja width/height */
2.card-slow {
3  width: 200px;
4  height: 300px;
5  transition: width 0.3s, height 0.3s;
6}
7
8.card-slow:hover {
9  width: 220px;    /* Reflow! */
10  height: 330px;   /* Reflow! */
11}
12
13/* ✅ SZYBKIE - animacja scale */
14.card-fast {
15  width: 200px;
16  height: 300px;
17  transform: scale(1);
18  transition: transform 0.3s;
19}
20
21.card-fast:hover {
22  transform: scale(1.1);  /* GPU! 220px × 330px */
23}

Przykład 3: Fade in/out

1/* ❌ WOLNE - animacja visibility */
2.modal-slow {
3  visibility: visible;
4  transition: visibility 0.3s;
5}
6
7.modal-slow.hidden {
8  visibility: hidden;  /* Nie można animować! Skok! */
9}
10
11/* ✅ SZYBKIE - animacja opacity */
12.modal-fast {
13  opacity: 1;
14  transition: opacity 0.3s;
15}
16
17.modal-fast.hidden {
18  opacity: 0;  /* Płynna animacja GPU! */
19  pointer-events: none;  /* Wyłącz interakcje */
20}

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

1/* ❌ BARDZO WOLNE - animacja box-shadow */
2.button-slow {
3  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
4  transition: box-shadow 0.3s;
5}
6
7.button-slow:hover {
8  box-shadow: 0 8px 16px rgba(0,0,0,0.3);  /* Bardzo ciężkie! */
9}
10
11/* ✅ SZYBSZE - pseudo-element z opacity */
12.button-fast {
13  position: relative;
14}
15
16.button-fast::after {
17  content: '';
18  position: absolute;
19  inset: 0;
20  box-shadow: 0 8px 16px rgba(0,0,0,0.3);
21  opacity: 0;
22  transition: opacity 0.3s;
23  pointer-events: none;
24}
25
26.button-fast:hover::after {
27  opacity: 1;  /* Animuj tylko opacity! */
28}

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

1.element {
2  will-change: transform;
3  /* Przeglądarka tworzy GPU layer PRZED animacją */
4}
5
6.element:hover {
7  transform: scale(1.2);  /* Już gotowe! */
8}

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

1/* ✅ DOBRE - dla elementów często animowanych */
2.menu-item {
3  will-change: transform;
4  transition: transform 0.2s;
5}
6
7.menu-item:hover {
8  transform: translateY(-5px);
9}
10
11/* ✅ DOBRE - dla animacji keyframes */
12.spinner {
13  will-change: transform;
14  animation: spin 1s linear infinite;
15}
16
17@keyframes spin {
18  to { transform: rotate(360deg); }
19}
20
21/* ✅ DOBRE - dodaj dynamicznie przed animacją */
22.modal {
23  /* Brak will-change domyślnie */
24}
25
26.modal.opening {
27  will-change: transform, opacity;  /* Dodaj przed animacją */
28}
29
30.modal.open {
31  will-change: auto;  /* Usuń po animacji! */
32}

Kiedy NIE używać will-change

1/* ❌ ZŁE - na wszystkich elementach */
2* {
3  will-change: transform;  /* Zużywa MASĘ pamięci! */
4}
5
6/* ❌ ZŁE - na statycznych elementach */
7.logo {
8  will-change: transform;  /* Logo się nie rusza! */
9}
10
11/* ❌ ZŁE - zbyt wiele właściwości */
12.element {
13  will-change: transform, opacity, width, height, background, color;
14  /* Zbyt dużo - przeciwskuteczne! */
15}
16
17/* ❌ ZŁE - zostawione na stałe */
18.button {
19  will-change: transform;  /* Zostawione na zawsze = leak pamięci */
20}

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)

1const element = document.querySelector('.box');
2
3// Dodaj przed animacją (np. na mouseenter)
4element.addEventListener('mouseenter', () => {
5  element.style.willChange = 'transform';
6});
7
8// Usuń po animacji (na transitionend)
9element.addEventListener('transitionend', () => {
10  element.style.willChange = 'auto';
11});

Force GPU acceleration - trick translateZ(0)

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

1.element {
2  /* Force GPU layer */
3  transform: translateZ(0);
4  /* lub */
5  transform: translate3d(0, 0, 0);
6  /* lub */
7  will-change: transform;
8}

Przykład praktyczny:

1/* Obrazek, który będzie zoomowany */
2.image {
3  transform: translateZ(0);  /* Force GPU */
4  transition: transform 0.3s;
5}
6
7.image:hover {
8  transform: scale(1.1);  /* Teraz gwarantowane GPU */
9}

⚠️ 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

1.progress-bar-slow {
2  width: 0%;
3  transition: width 2s;
4}
5
6.progress-bar-slow.complete {
7  width: 100%;  /* Reflow przy każdej klatce! */
8}

✅ Szybki sposób - transform: scaleX

1.progress-bar-fast {
2  width: 100%;  /* Pełna szerokość od razu */
3  transform: scaleX(0);  /* Skala 0% */
4  transform-origin: left;  /* Rośnij od lewej */
5  transition: transform 2s;
6}
7
8.progress-bar-fast.complete {
9  transform: scaleX(1);  /* Skala 100% - GPU! */
10}

✅ Alternatywa - clip-path

1.progress-bar-clip {
2  width: 100%;
3  background: linear-gradient(to right, #3498db, #2ecc71);
4  clip-path: inset(0 100% 0 0);  /* Ukryj prawą stronę */
5  transition: clip-path 2s;
6}
7
8.progress-bar-clip.complete {
9  clip-path: inset(0 0 0 0);  /* Pokaż wszystko */
10}

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
1/* CSS Animation - preferowane */
2.box {
3  animation: slide 1s ease-in-out infinite;
4}
5
6@keyframes slide {
7  0% { transform: translateX(0); }
8  100% { transform: translateX(100px); }
9}

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
1/* JavaScript rAF - dla złożonych przypadków */
2let position = 0;
3const box = document.querySelector('.box');
4
5function animate() {
6  position += 2;
7
8  // Używaj transform, nie left!
9  box.style.transform = `translateX(${position}px)`;
10
11  if (position < 500) {
12    requestAnimationFrame(animate);
13  }
14}
15
16requestAnimationFrame(animate);

❌ NIE używaj setInterval/setTimeout:

1/* ❌ ZŁE - setInterval */
2setInterval(() => {
3  position += 2;
4  box.style.left = position + 'px';  /* Nie zsynchronizowane z ekranem! */
5}, 16);
6
7/* ✅ DOBRE - requestAnimationFrame */
8function animate() {
9  position += 2;
10  box.style.transform = `translateX(${position}px)`;
11  requestAnimationFrame(animate);
12}
13requestAnimationFrame(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

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

Pokazuje:

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

3. Layers Panel

1DevTools → ⋮ (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

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

Przykłady optymalizacji - przed i po

Optymalizacja 1: Card hover effect

1/* ❌ PRZED - wolne (4 repaints, 2 reflows) */
2.card-before {
3  transition: all 0.3s;
4}
5
6.card-before:hover {
7  width: 320px;         /* Reflow */
8  box-shadow: 0 10px 30px rgba(0,0,0,0.3);  /* Repaint */
9  border: 2px solid gold;  /* Repaint */
10}
11
12/* ✅ PO - szybkie (0 reflows, 1 composite) */
13.card-after {
14  position: relative;
15  transition: transform 0.3s;
16}
17
18.card-after::before {
19  content: '';
20  position: absolute;
21  inset: -2px;
22  border: 2px solid gold;
23  opacity: 0;
24  transition: opacity 0.3s;
25}
26
27.card-after::after {
28  content: '';
29  position: absolute;
30  inset: 0;
31  box-shadow: 0 10px 30px rgba(0,0,0,0.3);
32  opacity: 0;
33  transition: opacity 0.3s;
34}
35
36.card-after:hover {
37  transform: scale(1.05);  /* GPU */
38}
39
40.card-after:hover::before,
41.card-after:hover::after {
42  opacity: 1;  /* GPU */
43}

Optymalizacja 2: Loading spinner

1/* ❌ PRZED - wolne (animacja border) */
2.spinner-before {
3  border: 4px solid #f3f3f3;
4  border-top: 4px solid #3498db;
5  border-radius: 50%;
6  animation: spin-slow 1s linear infinite;
7}
8
9@keyframes spin-slow {
10  to {
11    transform: rotate(360deg);
12    border-top-color: #e74c3c;  /* Animacja koloru = repaint! */
13  }
14}
15
16/* ✅ PO - szybkie (tylko transform) */
17.spinner-after {
18  border: 4px solid #f3f3f3;
19  border-top: 4px solid #3498db;
20  border-radius: 50%;
21  will-change: transform;
22  animation: spin-fast 1s linear infinite;
23}
24
25@keyframes spin-fast {
26  to {
27    transform: rotate(360deg);  /* Tylko GPU! */
28  }
29}

Optymalizacja 3: Smooth scroll reveal

1/* ❌ PRZED - wolne (animacja top + opacity) */
2.reveal-before {
3  position: relative;
4  top: 50px;
5  opacity: 0;
6  transition: top 0.6s, opacity 0.6s;
7}
8
9.reveal-before.visible {
10  top: 0;       /* Reflow */
11  opacity: 1;   /* GPU */
12}
13
14/* ✅ PO - szybkie (tylko transform + opacity) */
15.reveal-after {
16  transform: translateY(50px);
17  opacity: 0;
18  will-change: transform, opacity;
19  transition: transform 0.6s, opacity 0.6s;
20}
21
22.reveal-after.visible {
23  transform: translateY(0);  /* GPU */
24  opacity: 1;                /* GPU */
25}

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:

1/* Normalne animacje */
2.box {
3  transition: transform 0.3s;
4}
5
6.box:hover {
7  transform: scale(1.2) rotate(5deg);
8}
9
10/* Wyłącz dla użytkowników z prefers-reduced-motion */
11@media (prefers-reduced-motion: reduce) {
12  .box {
13    transition: none;  /* Brak animacji */
14  }
15
16  .box:hover {
17    transform: scale(1.05);  /* Tylko subtelna zmiana */
18  }
19}
20
21/* Lub globalnie */
22@media (prefers-reduced-motion: reduce) {
23  *,
24  *::before,
25  *::after {
26    animation-duration: 0.01ms !important;
27    animation-iteration-count: 1 !important;
28    transition-duration: 0.01ms !important;
29  }
30}

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!

Przejdź do CodeWorlds