Kurs HTML i CSS · Moduł 6: Animacje CSS
Wydajność animacji CSS - optymalizacja i GPU acceleration
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:
- Przeglądarka tworzy osobną warstwę (compositing layer) dla elementu
- Element jest renderowany przez GPU
- 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:
- Tylko dla animowanych elementów
- Tylko 1-2 właściwości jednocześnie
- Dodaj przed animacją, usuń po animacji
- 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:
- Otwórz DevTools (F12)
- Zakładka Performance
- Kliknij Record (czerwone kółko)
- Wykonaj animację
- Kliknij Stop
- 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 StatsPokazuje:
- FPS w czasie rzeczywistym
- GPU memory usage
- Paint flashing (czerwone prostokąty = repaint)
3. Layers Panel
DevTools → ⋮ (More tools) → LayersPokazuje:
- 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
transformiopacity - Unikam
width,height,margin,padding - Unikam
left,top,right,bottom - Unikam
box-shadow(lub animuję przez pseudo-element)
GPU Acceleration:
- Używam
transformzamiast pozycjonowania - Używam
scalezamiastwidth/height - Dodaję
will-changedla często animowanych elementów - Usuwam
will-changepo animacji
Optymalizacja:
- Preferuję CSS animations nad JavaScript
- Jeśli JS - używam
requestAnimationFrame, niesetInterval - 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)opacityfilter(blur, brightness - ale wolniejsze niż transform)
CPU-bound (WOLNE):
width,height,margin,paddingleft,top,right,bottomfont-size,border-widthbox-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
scalezamiastwidth/height - Używaj
translateX/Yzamiastleft/top - Używaj
opacityzamiastvisibility/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. Które właściwości CSS są przyspieszane przez GPU i najlepsze do płynnych animacji?
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):