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!
Problem: Animacje mogą powodować:
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.
Kluczowa różnica:
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:
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:
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:
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}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}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 to hint dla przeglądarki: "Hej, za chwilę będę animować tę właściwość, przygotuj się!"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/* ✅ 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}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:
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});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!
Problem: Animowanie
width lub height powoduje reflow przy każdej klatce.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}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}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}Zalety:
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}Kiedy używać:
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);Krok po kroku:
1DevTools → ⋮ (More tools) → Rendering → Frame Rendering StatsPokazuje:
1DevTools → ⋮ (More tools) → LayersPokazuje:
1/* Sprawdź ile elementów ma will-change */
2*[style*="will-change"] {
3 outline: 2px solid red !important;
4}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}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}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}Właściwości:
transform i opacitywidth, height, margin, paddingleft, top, right, bottombox-shadow (lub animuję przez pseudo-element)GPU Acceleration:
transform zamiast pozycjonowaniascale zamiast width/heightwill-change dla często animowanych elementówwill-change po animacjiOptymalizacja:
requestAnimationFrame, nie setIntervalMobile:
prefers-reduced-motionNiektó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}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:
Performant patterns:
scale zamiast width/heighttranslateX/Y zamiast left/topopacity zamiast visibility/displayTesting:
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!