Верстка сайту з використанням CSS-анімацій
CSS-анімації у браузері виконуються на композитору потоку — окремому потоці, що не блокує JavaScript і рендеринг. Правильно написані анімації не викликають перерахунку макета та працюють із 60 fps навіть на мобільних пристроях. Неправильно написані — знищують продуктивність, викликають jank і дратують користувача.
CSS transitions vs CSS animations: вибір інструмента
CSS transitions — для станів (hover, focus, active). Декларативно, коротко:
.button {
background-color: #2563eb;
transform: translateY(0);
transition:
background-color 200ms ease,
transform 150ms ease,
box-shadow 200ms ease;
}
.button:hover {
background-color: #1d4ed8;
transform: translateY(-2px);
box-shadow: 0 8px 25px rgb(37 99 235 / 0.4);
}
CSS animations (@keyframes) — для автономних, циклічних або багатокрокових рухів:
@keyframes pulse-ring {
0% {
transform: scale(0.8);
opacity: 0.8;
}
70% {
transform: scale(1.4);
opacity: 0;
}
100% {
transform: scale(1.4);
opacity: 0;
}
}
.live-indicator::before {
content: '';
position: absolute;
inset: -4px;
border-radius: 50%;
background: currentColor;
animation: pulse-ring 1.8s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
}
Тільки compositor-safe властивості
Анімувати слід лише властивості, що не викликають перерахунку макета або перефарбування:
| Властивість | Composite | Безпечно |
|---|---|---|
transform |
Так | Так |
opacity |
Так | Так |
filter |
Частково | Обережно |
width, height |
Ні | Ні, використовувати scale() |
top, left |
Ні | Ні, використовувати translate() |
background-color |
Ні | Тільки через transition |
clip-path |
Частково | Так, сучасні браузери |
Правило: переміщення через translate, масштабування через scale, обертання через rotate — ніколи top/left/width/height у @keyframes.
will-change: коли застосовувати
/* Правильно — на конкретний елемент, видалити після анімації */
.modal-overlay {
will-change: opacity;
}
.modal-overlay.is-visible {
will-change: auto; /* Звільнити ресурс після */
}
/* Неправильно — на всі підряд */
* {
will-change: transform; /* Вичерпає пам'ять GPU */
}
Паттерни анімацій
Skeleton loading
@keyframes skeleton-shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.skeleton {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: skeleton-shimmer 1.5s ease-in-out infinite;
border-radius: 4px;
}
Staggered list entrance
.list-item {
opacity: 0;
transform: translateY(16px);
animation: slide-up 400ms ease forwards;
}
@keyframes slide-up {
to {
opacity: 1;
transform: translateY(0);
}
}
/* Stagger через CSS custom property */
.list-item:nth-child(1) { animation-delay: calc(0 * 80ms); }
.list-item:nth-child(2) { animation-delay: calc(1 * 80ms); }
.list-item:nth-child(3) { animation-delay: calc(2 * 80ms); }
/* Або через inline style з JS/шаблонізатора */
/* style="--index: 3" → animation-delay: calc(var(--index) * 80ms) */
Спіннер завантаження
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
width: 20px;
height: 20px;
border: 2px solid rgb(0 0 0 / 0.1);
border-top-color: #2563eb;
border-radius: 50%;
animation: spin 600ms linear infinite;
}
Hero-секція з ефектом паралаксу через CSS
.hero {
perspective: 1px;
overflow: hidden;
height: 100svh;
}
.hero-bg {
transform: translateZ(-1px) scale(2);
/* Паралакс без JavaScript, тільки через CSS perspective */
}
Accessibility: prefers-reduced-motion
Обов'язково для будь-якого проекту:
/* Базова анімація */
.notification {
animation: bounce-in 600ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Відключення для користувачів з вестибулярними розладами */
@media (prefers-reduced-motion: reduce) {
.notification {
animation: fade-in 200ms ease;
}
/* Глобальне прискорення всіх анімацій */
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Intersection Observer + CSS класси
Анімації при появленні у viewport — без важких бібліотек:
// Мінімалістичний intersection observer
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target); // Однократно
}
});
},
{ threshold: 0.15 }
);
document.querySelectorAll('[data-animate]').forEach((el) => observer.observe(el));
[data-animate] {
opacity: 0;
transform: translateY(24px);
transition: opacity 500ms ease, transform 500ms ease;
}
[data-animate].is-visible {
opacity: 1;
transform: translateY(0);
}
Продуктивність: чек-лист
-
transformтаopacity— єдині властивості у@keyframesдля руху -
will-changeтільки на елементах із важкою анімацією, видаляти після -
animation-fill-mode: bothзамість дублювання початкового стану - Не більше 20–30 одночасно анімованих елементів на сторінці
-
prefers-reduced-motionохоплює всі анімації - Тестування в Chrome DevTools → Performance → Rendering → Paint flashing
Терміни
Базові CSS-переходи (стани hover, поява модалів, fade-ефекти): включаються в вартість базової верстки. Кастомні анімації (skeleton, staggered-списки, hero-анімації, паралакс): 0.5–1 день залежно від кількості та складності сцен.







