Вёрстка сайта с использованием CSS-анимаций
CSS-анимации в браузере исполняются на compositor thread — отдельном потоке, не блокирующем JavaScript и rendering. Грамотно написанные анимации не вызывают layout recalculation и работают в 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 свойства
Анимировать нужно только свойства, не вызывающие reflow/repaint:
| Свойство | 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-секция с parallax-эффектом через 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-анимации, parallax): 0.5–1 день в зависимости от количества и сложности сцен.







