Почему не работает CSS transition — 4 причины, из-за которых элемент прыгает вместо анимации

Ты написал красивую анимацию:
.menu { transition: all 0.3s ease; }
Жмёшь кнопку — меню появляется. Мгновенно. Никаких 0.3 секунды. Ты увеличиваешь время до 3 секунд — всё равно мгновенно.
Смотри, что тут обидно: CSS не выдаёт никакой ошибки. Transition просто молча не запускается. Причина почти всегда одна из четырёх — разберём от самой частой.
Причина 1: ты переключаешь display
Самая частая. Меню скрыто через display: none, по клику становится display: block.
Как проверить. Посмотри, какое свойство меняется между состояниями. Если среди них display — это оно.
Почему не работает. Для анимации браузеру нужны промежуточные значения. Между «0» и «1» есть «0.5». А между «нет элемента» и «есть элемент» — нет ничего. display переключается рывком, и всё, что меняется вместе с ним, тоже прыгает. Пока элемент был display: none, у него не было отрисованного «начального» вида.
Как починить. Классический способ — не трогать display, а анимировать opacity и visibility:
.menu {
opacity: 0;
visibility: hidden;
transition: opacity 0.3s, visibility 0.3s;
}
.menu.open {
opacity: 1;
visibility: visible;
}
visibility умеет переключаться в конце анимации, поэтому меню сначала плавно гаснет и лишь потом перестаёт ловить клики. Подробнее про разницу — в разборе display: none или visibility: hidden.
Современный способ — разрешить анимацию «дискретных» свойств. Работает в Chrome 117+, Firefox 129+ и Safari 17.5+:
.menu {
display: none;
opacity: 0;
transition: opacity 0.3s, display 0.3s allow-discrete;
}
.menu.open { display: block; opacity: 1; }
@starting-style {
.menu.open { opacity: 0; } /* с чего начинать при появлении */
}
Причина 2: ты анимируешь до height: auto
Аккордеон: закрытый — height: 0, открытый — height: auto. Открывается рывком.
Как проверить. Поставь вместо auto число, например height: 200px. Анимация заработала — значит, дело в auto.
Почему не работает. auto — это не число, а «посчитай сам». Браузер не умеет делить пополам «посчитай сам», поэтому промежуточных кадров нет.
Как починить. Самый надёжный трюк сейчас — анимировать строку сетки. 0fr и 1fr — числа, и они анимируются:
.panel {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.3s;
}
.panel.open { grid-template-rows: 1fr; }
.panel > .inner { overflow: hidden; } /* без этого содержимое торчит наружу */
Работает во всех современных браузерах. Есть и новое свойство interpolate-size: allow-keywords, которое учит браузер анимировать прямо до auto. Но пока его поддерживают только Chrome и Edge, так что как основное решение рано.
Не делай max-height: 9999px. Анимация пойдёт по всем 9999 пикселям, и закрытие будет «залипать» с задержкой.
Причина 3: элемент и класс появились в одном кадре
Ты создаёшь тост в JavaScript, добавляешь на страницу и сразу вешаешь класс:
const toast = document.createElement("div");
toast.className = "toast"; // opacity: 0
document.body.append(toast);
toast.classList.add("visible"); // opacity: 1 — но анимации нет
Почему не работает. Браузер не рисует каждую строчку отдельно. Он собирает все изменения и рисует один раз. До экрана элемент сразу доехал с opacity: 1 — состояния «0» он не видел, значит, анимировать не из чего.
Как проверить. Добавь класс через setTimeout(..., 50). Если так анимация пошла — это твой случай.
Как починить. Лучший способ — тот же @starting-style из первой причины: в CSS прямо написано, с чего начинать. Без него — дождись, пока браузер отрисует начальное состояние:
document.body.append(toast);
requestAnimationFrame(() => {
requestAnimationFrame(() => toast.classList.add("visible"));
});
Два requestAnimationFrame подряд гарантируют, что первый кадр уже нарисован. setTimeout с паузой тоже работает, но это лотерея с таймингом.
Причина 4: анимации выключены нарочно
Анимация не работает только у тебя — или только на одном компьютере.
Как проверить. Поищи в стилях prefers-reduced-motion. Многие CSS-сбросы содержат правило «при включённом в системе уменьшении движения — transition: none !important». Включена эта настройка в ОС — все анимации на сайте пропадают.
Как починить. Ничего чинить не надо: это уважение к людям, которых от движения укачивает. Просто проверь настройку у себя. А как делать такие режимы правильно — в статье про prefers-reduced-motion.
Как быстро найти причину?
Открой DevTools, выдели элемент и посмотри вкладку Computed: там видно итоговое значение transition. Если стиль перебит другим правилом — ищи, кто его перебил; про такие перебивки есть отдельный разбор, почему CSS не применяется.
transition: all — это плохо?
Не ошибка, но риск: анимироваться начнёт всё, включая то, что ты не планировал, и это тормозит. Перечисляй свойства явно: transition: opacity 0.3s, transform 0.3s.
Почему анимация есть при наведении, а при уходе курсора нет?
Скорее всего, transition написан только в правиле :hover. При уходе курсора это правило перестаёт действовать — и transition вместе с ним. Перенеси transition в основное правило элемента.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.




