Практика анимаций
Готовые рецепты CSS-анимаций: спиннеры, hover-эффекты, анимации появления и доступность через
prefers-reduced-motion.
Зачем нужно
Теория анимаций бесполезна без практики. Этот файл — коллекция проверенных паттернов, которые можно копировать в проекты. Каждый рецепт учитывает производительность и доступность.
Где используется
- Загрузка и ожидание (спиннеры, skeleton)
- Интерактивные элементы (hover, click)
- Появление контента при скролле
- Уведомления и alerts
- Навигация и меню
Предпосылки
- transition -- плавные переходы — переходы
- transform -- rotate, scale, translate — трансформации
- Keyframes — @keyframes и animation
Спиннеры
Круговой спиннер
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #e9ecef;
border-top-color: #007bff;
border-radius: 50%;
animation: spin 800ms linear infinite;
}
Три точки
@keyframes dotPulse {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
.dots {
display: flex;
gap: 8px;
}
.dot {
width: 12px;
height: 12px;
background: #007bff;
border-radius: 50%;
animation: dotPulse 1.4s ease-in-out infinite;
}
.dot:nth-child(1) { animation-delay: 0ms; }
.dot:nth-child(2) { animation-delay: 160ms; }
.dot:nth-child(3) { animation-delay: 320ms; }
Skeleton-загрузка
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.skeleton {
background: linear-gradient(
90deg,
#e9ecef 25%,
#f8f9fa 50%,
#e9ecef 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s ease-in-out infinite;
border-radius: 4px;
}
.skeleton-text {
height: 16px;
margin-bottom: 8px;
}
.skeleton-title {
height: 24px;
width: 60%;
margin-bottom: 16px;
}
Hover-эффекты
Приподнятие карточки
.card {
transition: transform 300ms ease, box-shadow 300ms ease;
}
.card:hover {
transform: translateY(-8px);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.12);
}
Масштабирование изображения
.image-container {
overflow: hidden;
border-radius: 12px;
}
.image-container img {
transition: transform 400ms ease;
}
.image-container:hover img {
transform: scale(1.08);
}
Заполняющийся фон кнопки
.btn-fill {
position: relative;
padding: 12px 28px;
color: #007bff;
border: 2px solid #007bff;
background: transparent;
overflow: hidden;
cursor: pointer;
z-index: 1;
transition: color 300ms ease;
}
.btn-fill::before {
content: "";
position: absolute;
inset: 0;
background: #007bff;
transform: scaleX(0);
transform-origin: left;
transition: transform 300ms ease;
z-index: -1;
}
.btn-fill:hover {
color: white;
}
.btn-fill:hover::before {
transform: scaleX(1);
}
Подчёркивание ссылки
.link-underline {
position: relative;
text-decoration: none;
color: #333;
}
.link-underline::after {
content: "";
position: absolute;
bottom: -2px;
left: 0;
width: 0;
height: 2px;
background: #007bff;
transition: width 300ms ease;
}
.link-underline:hover::after {
width: 100%;
}
Анимации появления (entrance)
Fade-in снизу
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-in {
opacity: 0;
animation: fadeInUp 600ms ease-out forwards;
}
Slide-in сбоку
@keyframes slideInLeft {
from {
opacity: 0;
transform: translateX(-60px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.slide-left {
animation: slideInLeft 500ms ease-out forwards;
}
Scale-in (из центра)
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
.scale-in {
animation: scaleIn 400ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
Staggered (каскадная) анимация
.stagger-item {
opacity: 0;
animation: fadeInUp 500ms ease-out forwards;
animation-delay: calc(var(--i) * 80ms);
}
<div class="stagger-item" style="--i: 0">Первый</div>
<div class="stagger-item" style="--i: 1">Второй</div>
<div class="stagger-item" style="--i: 2">Третий</div>
<div class="stagger-item" style="--i: 3">Четвёртый</div>
Уведомления
Slide-in сверху
@keyframes slideDown {
from { transform: translateY(-100%); }
to { transform: translateY(0); }
}
@keyframes slideUp {
from { transform: translateY(0); }
to { transform: translateY(-100%); }
}
.toast {
position: fixed;
top: 20px;
right: 20px;
padding: 16px 24px;
background: #333;
color: white;
border-radius: 8px;
animation:
slideDown 300ms ease-out,
slideUp 300ms ease-in 2700ms forwards;
}
Shake (ошибка ввода)
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
20%, 40%, 60%, 80% { transform: translateX(4px); }
}
.input-error {
animation: shake 500ms ease;
border-color: #dc3545;
}
prefers-reduced-motion — доступность
Некоторые пользователи испытывают головокружение или тошноту от анимаций (вестибулярные нарушения). CSS медиа-запрос
prefers-reduced-motionпозволяет отключить или упростить анимации для таких пользователей.
Полное отключение
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
Замена тяжёлых анимаций на лёгкие
.card {
transition: transform 300ms ease, box-shadow 300ms ease;
}
.card:hover {
transform: translateY(-8px);
}
@media (prefers-reduced-motion: reduce) {
.card {
transition: opacity 200ms ease;
}
.card:hover {
transform: none;
opacity: 0.8;
}
}
Подход «motion-first»
/* Базово — без анимации */
.element {
opacity: 1;
}
/* Анимации только для тех, кто не против */
@media (prefers-reduced-motion: no-preference) {
.element {
animation: fadeInUp 600ms ease-out forwards;
}
}
Советы по производительности
/* Анимируйте ТОЛЬКО transform и opacity */
/* ХОРОШО */
.element {
transition: transform 300ms, opacity 300ms;
}
/* ПЛОХО — вызывает layout recalculation */
.element {
transition: width 300ms, height 300ms, top 300ms, left 300ms;
}
/* will-change для тяжёлых анимаций */
.heavy-animation {
will-change: transform;
}
/* Убрать will-change после анимации */
.heavy-animation.done {
will-change: auto;
}
Частые ошибки
- Слишком много анимаций одновременно — тормозит UI, особенно на мобильных
- Забыли
prefers-reduced-motion— проблемы с доступностью - Анимация
width/heightвместоtransform— вызывает reflow will-changeна всех элементах — потребляет память GPU- Анимации без
forwards— элемент «прыгает» обратно
Практика
- Создать 3 варианта спиннера (круг, точки, skeleton)
- Сделать hover-эффект с заполняющимся фоном кнопки
- Реализовать staggered-анимацию списка
- Добавить
prefers-reduced-motionко всем анимациям - Создать toast-уведомление с автоскрытием
Связанные темы
- transition -- плавные переходы — базовые переходы
- transform -- rotate, scale, translate — трансформации
- Keyframes — определение анимаций
- Media queries — медиа-запросы
- Псевдоклассы —
:hover,:focusдля триггеров