Основа: transitions и keyframes
Это и так знают все, но стоит освежить — потому что именно на этом строится всё остальное.
Transitions — плавное изменение одного состояния в другое при смене класса или псевдокласса:
.button {
background: #1a1a2e;
transform: scale(1);
transition: background 0.2s ease, transform 0.15s ease;
}
.button:hover {
background: #e94560;
transform: scale(1.03);
}
Правило которое экономит много времени: анимируй только transform и opacity. Они работают на GPU и не вызывают reflow. top, left, width, height — всё это дорого и заметно на мобильных устройствах.
Keyframes — анимация по ключевым кадрам, запускается автоматически или по классу:
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: slideUp 0.4s ease forwards;
}
forwards — важный параметр: без него элемент вернётся в исходное состояние после окончания анимации.
@starting-style — появление элементов без вспышки
Раньше если нужно было анимировать появление элемента при добавлении в DOM — это был JavaScript: добавить класс через requestAnimationFrame, иначе браузер не видит разницу начального и конечного состояния.
@starting-style решает это в чистом CSS. Он задаёт стартовые значения свойств в момент когда элемент только появляется на странице:
.toast {
opacity: 1;
transform: translateY(0);
transition: opacity 0.3s ease, transform 0.3s ease,
display 0.3s allow-discrete;
}
@starting-style {
.toast {
opacity: 0;
transform: translateY(12px);
}
}
Элемент появляется — анимируется от @starting-style к основным значениям. Плавно, без JS, без setTimeout.
Кроссбраузерный базовый уровень достигнут в 2026 — Safari, Firefox, Chrome поддерживают.
display: none с анимацией выхода
Самая старая боль в CSS: анимировать исчезновение элемента нельзя — display: none отключает его мгновенно, раньше чем переход успевает сыграть. Стандартное решение — setTimeout на длину анимации. Хрупко и ненадёжно.
Теперь работает через transition-behavior: allow-discrete:
.dropdown {
opacity: 1;
transform: translateY(0);
transition: opacity 0.2s ease, transform 0.2s ease,
display 0.2s allow-discrete;
}
.dropdown[hidden] {
display: none;
opacity: 0;
transform: translateY(-8px);
}
@starting-style {
.dropdown {
opacity: 0;
transform: translateY(-8px);
}
}
allow-discrete говорит браузеру: дождись окончания перехода, только потом переключай дискретное свойство (display). Работает для тостов, дропдаунов, тултипов — любого элемента который появляется и исчезает.
interpolate-size — height: auto наконец анимируется
Аккордеон, раскрывающийся плавно — один из самых частых запросов. Раньше нужно было измерять scrollHeight в JavaScript и анимировать к конкретному значению в пикселях. Ломалось при изменении контента.
Одна строка в :root меняет это:
:root {
interpolate-size: allow-keywords;
}
.panel {
height: 0;
overflow: clip;
transition: height 0.35s ease;
}
.panel[data-open] {
height: auto;
}
interpolate-size: allow-keywords разрешает браузеру интерполировать к ключевым словам: auto, min-content, fit-content. Это свойство наследуется, поэтому одного объявления на :root достаточно для всего проекта.
Важно: overflow: clip вместо overflow: hidden — clip не создаёт случайный scroll-контейнер который мешает анимации высоты.
Scroll-driven animations — скролл как таймлайн
Это самое мощное из всего что появилось. Анимация прогрессирует не по времени, а по позиции скролла. Никакого JavaScript, никакого IntersectionObserver, никакого GSAP для базовых scroll-эффектов.
Два типа timeline:
scroll() — прогресс зависит от позиции в скролл-контейнере. Идеально для progress bar или parallax-фона:
@keyframes progressBar {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.reading-progress {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 3px;
background: #e94560;
transform-origin: left;
animation: progressBar linear;
animation-timeline: scroll(root);
}
Полоска прогресса чтения без единой строки JS. Реагирует на скролл плавно, работает на compositor thread.
view() — прогресс зависит от позиции элемента во вьюпорте. Идеально для reveal-эффектов при скролле:
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(24px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.section {
animation: fadeInUp linear both;
animation-timeline: view();
animation-range: entry 0% entry 30%;
}
animation-range: entry 0% entry 30% — анимация играет пока элемент входит во вьюпорт, занимая первые 30% этого входа. После этого остаётся в конечном состоянии.
Поддержка в 2026 — кроссбраузерная. Firefox и Safari закрыли поддержку в 2025–2026, без префиксов.
Scroll-triggered animations — запуск по порогу скролла
В Chrome 145 появилось отдельное от scroll-driven: animation-trigger. Это не таймлайн где анимация скрабится скроллом — это однократный запуск когда элемент пересекает заданный порог. Прощай IntersectionObserver для этого паттерна:
@keyframes popIn {
from { opacity: 0; scale: 0.9; }
to { opacity: 1; scale: 1; }
}
.card {
animation: popIn 0.4s ease forwards paused;
animation-trigger: view(block 20%);
}
paused — анимация ждёт. animation-trigger: view(block 20%) — запускает когда элемент вошёл на 20% вьюпорта. Один раз, не скрабится.
На момент июня 2026 — Chrome 145+. Firefox и Safari в процессе. Добавляй @supports:
@supports (animation-trigger: view()) {
.card { animation-trigger: view(block 20%); }
}
@property — анимация градиентов и кастомных значений
Градиенты в CSS нельзя анимировать через transition — браузер не понимает как интерполировать между двумя linear-gradient(). Обходной путь был через псевдоэлементы с opacity. @property решает это напрямую, регистрируя кастомное свойство с типом:
@property --gradient-angle {
syntax: " ";
inherits: false;
initial-value: 0deg;
}
.card {
background: linear-gradient(var(--gradient-angle), #1a1a2e, #e94560);
transition: --gradient-angle 0.6s ease;
}
.card:hover {
--gradient-angle: 180deg;
}
Теперь при hover угол градиента плавно поворачивается — без JS, без псевдоэлементов. То же работает для анимации цветов внутри градиента, теней и любых значений которые раньше не поддавались переходам.
@property также открывает анимацию счётчиков, цветовых компонентов и любых числовых CSS-переменных. Поддержка — кроссбраузерная.
View Transitions — переходы между страницами
document.startViewTransition() — один из самых эффектных API которые появились за последние годы. Плавный переход между состояниями DOM или целыми страницами:
document.startViewTransition(() => {
updateDOM(); // любое изменение DOM
});
По умолчанию браузер делает crossfade. Кастомизация через CSS:
/* Переход между страницами — slide */
::view-transition-old(root) {
animation: slide-out 0.3s ease forwards;
}
::view-transition-new(root) {
animation: slide-in 0.3s ease forwards;
}
@keyframes slide-out {
to { transform: translateX(-100%); }
}
@keyframes slide-in {
from { transform: translateX(100%); }
}
Для многостраничных сайтов — автоматические переходы через @view-transition { navigation: auto; } в CSS без JavaScript вообще.
prefers-reduced-motion — обязательно
Все анимации выше нужно оборачивать в проверку. Часть пользователей отключает анимации в системных настройках — по медицинским причинам или личным предпочтениям:
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
}
}
Одно правило в конце глобального CSS — и все анимации деградируют до мгновенных переходов у тех кто их отключил.
AI помогает: промпты под конкретные задачи
Весь код выше отлично генерируется агентами — CSS хорошо представлен в обучающих данных и новые свойства тоже подхватились. Три промпта под частые задачи:
Reveal-анимации при скролле без JS:
Добавь анимации появления при скролле для всех элементов
с классом .reveal на странице.
Используй только CSS: animation-timeline: view() и animation-range.
Анимация: opacity 0→1, translateY 20px→0, duration 0.5s ease.
Не использовать JavaScript, IntersectionObserver или сторонние библиотеки.
Добавь @media (prefers-reduced-motion: reduce) с отключением анимаций.
Аккордеон с плавным открытием до height: auto:
Создай компонент аккордеона на чистом HTML и CSS.
Требования:
- Открытие/закрытие плавно анимирует height до auto без JS-измерений
- Используй interpolate-size: allow-keywords на :root
- Анимация появления контента через @starting-style
- Переключение только через CSS :has() или details/summary без JavaScript
- Поддержка prefers-reduced-motion
Переход между страницами через View Transitions:
Добавь плавные переходы при навигации между страницами
в Next.js / React Router проекте.
Используй View Transitions API: document.startViewTransition().
Переход: текущая страница уходит влево, новая приходит справа.
CSS для ::view-transition-old и ::view-transition-new.
Добавь fallback: если API не поддерживается — обычная навигация без анимации.
Не использовать Framer Motion или другие библиотеки для этого эффекта.
Когда всё-таки нужна библиотека
CSS не заменяет всё. Три сценария где библиотека оправдана:
Физика и пружины — spring() easing в CSS пока не стандартизирован. Если нужен отскок с реальной физикой — React Spring или GSAP.
Сложные скрабируемые таймлайны — несколько анимаций синхронизированных на одном таймлайне с паузами, повторами и динамическим управлением — GSAP ScrollTrigger.
Интерактивные состояния с жестами — свайп, drag, pinch — это всегда JavaScript. CSS не обрабатывает gesture-события.
Во всём остальном — сначала браузер.
Источник и полная версия: VibeCode Wiki