animation-direction задаёт, в какую сторону проигрывается цикл анимации. normal (по умолчанию) всегда идёт вперёд, reverse - всегда назад (и разворачивает саму кривую плавности). alternate меняет направление на каждом цикле - вперёд, потом назад, потом снова вперёд, а alternate-reverse делает то же самое, но начиная с обратного хода.
В этом уроке
Попробуй прямо сейчас: шарик плавно катается туда-обратно по дорожке - это animation-direction: alternate заставляет каждый второй проход идти в обратную сторону, хотя в @keyframes move описан только путь ТУДА (0% и 100%). Поменяй animation-direction на normal - шарик доедет до конца и на каждом цикле будет РЕЗКО перепрыгивать обратно к началу, вместо плавного возврата. Попробуй и reverse - шарик стартует сразу с конца дорожки и едет к началу.
normal и reverse: один проход в одну сторону
normal - значение по умолчанию: каждый цикл анимация идёт от 0% к 100% и, закончив, сразу начинает заново с 0%. reverse проигрывает цикл ЦЕЛИКОМ НАОБОРОТ - от 100% к 0%, и на каждом повторе снова стартует с конца. Менее очевидный момент, подтверждённый собственной проверкой: reverse разворачивает не только путь, но и саму функцию плавности из урока про кривые Безье - ease-in (медленный старт) при reverse ведёт себя как ease-out (медленный финиш), а не как ease-in задом наперёд.
.badge {
animation-name: slide;
animation-duration: 1s;
animation-direction: reverse;
} - анимация slide проиграется от 100% к 0%, будто @keyframes написаны наоборот.
Неправильное ожидание - что reverse влияет только на путь, но не на плавность:
.badge {
animation-name: slide;
animation-timing-function: ease-in;
animation-direction: reverse;
} - на самом деле ease-in тоже развернётся и будет ощущаться как ease-out.
alternate и alternate-reverse: качели на каждом цикле
alternate меняет направление НА КАЖДОМ цикле: 1-й проход вперёд, 2-й назад, 3-й снова вперёд - и так далее (счёт нечётности/чётности начинается с первого прохода). alternate-reverse - зеркальная пара: 1-й проход назад, 2-й вперёд. Из-за этого правила есть частая ловушка: если не указать animation-iteration-count больше 1 (по умолчанию он равен 1), alternate успевает проиграть только свой единственный - "нечётный" - проход, то есть выглядит АБСОЛЮТНО ТАК ЖЕ, как normal; то же самое с alternate-reverse и reverse. Разница становится заметна только при animation-iteration-count: 2 и больше (или infinite). Дробные значения iteration-count тоже подчиняются этому правилу чётности - например при 2.5 и alternate третий (частичный) проход снова будет ВПЕРЁД, потому что 3 - нечётное число.
.hint {
animation-name: bounce;
animation-duration: .8s;
animation-iteration-count: infinite;
animation-direction: alternate;
} - стрелка плавно качается вниз-вверх, качели видны, потому что итераций много.
Неправильное ожидание - что alternate что-то изменит с одним проходом:
.hint {
animation-name: bounce;
animation-duration: .8s;
animation-direction: alternate;
} - animation-iteration-count не указан (по умолчанию 1), поэтому alternate visually неотличим от normal - качелей не будет, стрелка просто один раз качнётся вперёд.
Частые ошибки новичков
| Ошибка | Что происходит | Как исправить |
|---|---|---|
| Указать animation-direction: alternate, но забыть про animation-iteration-count | По умолчанию iteration-count равен 1 - alternate успевает сыграть только один (нечётный) проход и выглядит как normal, качелей не видно | Указывать animation-iteration-count: infinite или конкретное число 2+ |
| Считать, что reverse разворачивает только путь | Функция плавности тоже разворачивается - ease-in при reverse ведёт себя как ease-out | Учитывать разворот кривой при выборе timing-function вместе с reverse |
| Путать alternate и alternate-reverse | У alternate первый проход вперёд, у alternate-reverse - первый проход назад (ровно наоборот) | Проверять первый цикл визуально или через getAnimations() перед сдачей верстки |
| Вручную дублировать обратный путь в @keyframes, когда можно обойтись alternate | Лишний код: два набора процентов вместо одного плюс animation-direction | Описать только путь ТУДА в @keyframes и указать animation-direction: alternate для обратного хода |
Шпаргалка
| Запись | Что означает |
|---|---|
| animation-direction: normal; | Каждый цикл вперёд, от 0% к 100% (по умолчанию) |
| animation-direction: reverse; | Каждый цикл назад, от 100% к 0%, плюс разворот функции плавности |
| animation-direction: alternate; | 1-й проход вперёд, 2-й назад, 3-й вперёд... |
| animation-direction: alternate-reverse; | 1-й проход назад, 2-й вперёд, 3-й назад... |
| alternate + iteration-count: 1; | Выглядит как normal - разницы не видно |
| alternate + iteration-count: infinite; | Бесконечные плавные качели туда-обратно |
Коротко: отвечаем на вопросы
Как сделать, чтобы анимация при наведении плавно возвращалась в исходное положение, а не обрывалась резко?
Реальный совет с Хабра - не дописывать обратный путь вручную в @keyframes, а использовать animation-direction: alternate вместе с animation-iteration-count: 2 (например прямо на самой анимации при :hover). Браузер сам проиграет путь в обратную сторону, и переход получится плавным, без резкого прыжка к началу.
Почему при animation-direction: reverse кривая плавности выглядит совсем не так, как я ожидал?
Потому что reverse разворачивает не только путь, но и саму функцию плавности - направленная кривая вроде ease-in (медленный старт, быстрый финиш) при развороте ведёт себя как ease-out (быстрый старт, медленный финиш), а не как исходная кривая, проигранная задом наперёд. Это задокументированная особенность CSS-анимаций, а не баг браузера.