Олег Марков
CSS анимации - css-animations от базовых примеров до продвинутых техник
Введение
CSS анимации позволяют «оживлять» интерфейс без JavaScript. Вы можете плавно изменять положение элементов, их размер, цвет, прозрачность и десятки других свойств. Главное преимущество в том, что браузер сам управляет анимацией и может оптимизировать ее под устройство пользователя.
В этой статье вы увидите, как шаг за шагом работать с CSS анимациями: от простых эффектов наведения до сложных последовательных и бесконечных анимаций. Мы разберем ключевые кадры, свойства animation-*, различия между transition и animation, а также обсудим настройки производительности, которые стоит учитывать в реальных проектах.
Что такое CSS анимации и чем они отличаются от transition
Переходы (transition) против анимаций (animation)
Для начала давайте разведем два похожих механизма:
transition— анимация запускается при изменении состояния (например, при:hover,:focus, добавлении класса).animation— анимация может идти сама по себе, по циклу, запускаться при загрузке страницы и не требует смены состояния.
Коротко различия в поведении:
transition:- Нужны исходное и конечное состояние.
- Запускается при изменении CSS-свойства.
- Обычный сценарий — плавный переход по наведению.
animation:- Набор промежуточных состояний в
@keyframes. - Может запускаться автоматически при загрузке.
- Может быть бесконечной, обратимой, управляемой задержкой и направлением.
- Набор промежуточных состояний в
Смотрите, я покажу вам маленькое сравнение.
Пример с transition
.button {
background-color: #3498db;
transition: background-color 0.3s ease; /* Плавное изменение цвета */
}
.button:hover {
background-color: #2ecc71; /* При наведении меняем цвет */
}
<button class="button">Наведи на меня</button>
Здесь анимация есть, но вы ее не описываете отдельно. Браузер автоматически интерполирует цвет от синего к зеленому при наведении и обратно при уходе курсора.
Пример с animation
@keyframes pulse {
0% {
transform: scale(1); /* Начальный размер */
}
50% {
transform: scale(1.1); /* Увеличение */
}
100% {
transform: scale(1); /* Возврат к исходному размеру */
}
}
.button-pulse {
animation-name: pulse; /* Имя анимации */
animation-duration: 1s; /* Длительность одного цикла */
animation-iteration-count: infinite; /* Бесконечное повторение */
}
<button class="button-pulse">Я пульсирую</button>
Как видите, здесь вы явно задаете все ключевые состояния. Запуск происходит сам по себе — ничего не нужно «триггерить» через hover.
Основы CSS анимаций: @keyframes и базовые свойства
Объявление ключевых кадров с @keyframes
В основе CSS анимаций лежит правило @keyframes. В нем вы описываете, как будут меняться свойства элемента на протяжении анимации.
Общий вид:
@keyframes имяАнимации {
0% {
/* Начальное состояние */
}
50% {
/* Промежуточное состояние */
}
100% {
/* Конечное состояние */
}
}
- Имя анимации — произвольное (латиница, цифры, дефисы), например
fade-in,slideUp,spin. - Вы можете использовать проценты (
0%,25%,50%,100%) или ключевые словаfromиto:from=0%to=100%
Давайте разберемся на простом примере появления блока с постепенным увеличением непрозрачности.
/* Объявляем анимацию плавного появления */
@keyframes fade-in {
from {
opacity: 0; /* Невидим в начале */
}
to {
opacity: 1; /* Полностью видим в конце */
}
}
/* Применяем к элементу */
.box {
opacity: 0; /* Стартовое состояние для гарантии */
animation-name: fade-in; /* Указываем, какую анимацию применить */
animation-duration: 1s; /* Длительность анимации */
animation-fill-mode: forwards; /* Сохраняем конечное состояние */
}
<div class="box">Я появлюсь плавно</div>
Комментарии в коде помогают вам увидеть, какие свойства за что отвечают. Мы еще вернемся к animation-fill-mode, потому что это важный момент.
Свойства семейства animation
Свойств в группе animation-* довольно много. Сейчас посмотрим главное, а дальше пойдем глубже.
Основные:
animation-name— имя анимации (@keyframes), которую вы хотите использовать.animation-duration— длительность одного цикла (например1s,500ms).animation-timing-function— функция временного распределения (напримерease,linear,ease-in-out).animation-delay— задержка перед стартом анимации.animation-iteration-count— количество повторений (1,3,infinite).animation-direction— направление (normal,reverse,alternate,alternate-reverse).animation-fill-mode— что происходит с элементом до и после анимации.animation-play-state— статус воспроизведения (runningилиpaused).
И есть сокращенное свойство animation, которое позволяет задать все сразу. Чуть позже я покажу вам удобный способ пользоваться шорткатом.
Подробный разбор @keyframes
Использование нескольких ключевых точек
Ключевые кадры не ограничиваются только началом и концом. Вы можете описать целую последовательность состояний.
@keyframes move-and-fade {
0% {
transform: translateX(0); /* Стартовая позиция */
opacity: 0; /* Невидим */
}
25% {
transform: translateX(50px); /* Сместился немного */
opacity: 0.5; /* Полупрозрачен */
}
50% {
transform: translateX(100px);/* Сместился дальше */
opacity: 1; /* Полностью видим */
}
100% {
transform: translateX(0); /* Вернулся на место */
opacity: 1; /* Все еще видим */
}
}
.element {
animation: move-and-fade 2s ease-in-out forwards;
}
Как видите, этот код выполняет одновременно два эффекта: элемент передвигается по оси X и меняет прозрачность. Между этими точками браузер сам «дорисовывает» промежуточные состояния.
Несколько свойств в одних ключевых кадрах
Внутри блока @keyframes вы можете менять сразу несколько свойств:
@keyframes complex-example {
0% {
transform: translateY(0) scale(1); /* Начало */
opacity: 0;
background-color: #3498db;
}
50% {
transform: translateY(-20px) scale(1.05); /* Подскочил и увеличился */
opacity: 1;
background-color: #9b59b6;
}
100% {
transform: translateY(0) scale(1); /* Вернулся в исходное положение */
opacity: 1;
background-color: #2ecc71;
}
}
.card {
animation: complex-example 1.5s ease-out forwards;
}
Браузер интерполирует и цвета, и размеры, и прозрачность. Это удобно для сложных визуальных эффектов.
Несколько анимаций на одном элементе
Один элемент может участвовать сразу в нескольких анимациях. Для этого значения свойств animation-* перечисляются через запятую.
Покажу вам, как это выглядит в коде.
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slide-down {
from {
transform: translateY(-20px);
}
to {
transform: translateY(0);
}
}
.block {
/* Две анимации применяются одновременно */
animation-name: fade-in, slide-down; /* Две анимации */
animation-duration: 0.8s, 0.8s; /* Длительность для каждой */
animation-timing-function: ease-out, ease-out; /* Кривая для каждой */
animation-fill-mode: forwards, forwards; /* Обе сохраняют финальное состояние */
}
Здесь я размещаю пример с двумя анимациями, чтобы вам было проще понять принцип. Порядок значений должен соответствовать порядку имен.
Для сокращенного синтаксиса можно написать:
.block {
animation:
fade-in 0.8s ease-out forwards,
slide-down 0.8s ease-out forwards;
}
Такая запись проще читается и часто используется в продакшне.
Свойства animation: детальный разбор
animation-duration
Отвечает за продолжительность одного полного цикла анимации.
.box {
animation-name: fade-in;
animation-duration: 2s; /* Длительность 2 секунды */
}
- Значения:
s(секунды),ms(миллисекунды). - Не может быть отрицательным.
- Если не указано, по умолчанию
0s— анимация не проиграется.
animation-timing-function
Управляет тем, как меняется скорость анимации во времени.
Основные значения:
linear— равномерная скорость.ease— стандартная кривая браузера (ускорение, затем замедление).ease-in— плавный старт, затем ускорение.ease-out— быстрое начало, плавное завершение.ease-in-out— плавный старт и плавное окончание.steps(n, start|end)— ступенчатая анимация (без плавных переходов).
Давайте посмотрим на примере ступенчатой анимации, которая часто используется для «печатающего» текста.
@keyframes typing {
from {
width: 0; /* Начинаем с нулевой ширины */
}
to {
width: 20ch; /* Показываем 20 символов */
}
}
.text-typing {
overflow: hidden; /* Скрываем лишний текст */
white-space: nowrap; /* Запрещаем перенос строк */
animation: typing 4s steps(20, end) forwards; /* 20 «шагов» - по символу */
}
<p class="text-typing">
Этот текст будет появляться как при печати.
</p>
Комментарии поясняют, что steps(20, end) будет обновлять ширину кусками, создавая эффект «символ за символом».
animation-delay
Задержка перед запуском анимации. Используется, чтобы создавать очередность.
.item {
animation: fade-in 1s ease forwards;
}
.item:nth-child(1) {
animation-delay: 0s; /* Без задержки */
}
.item:nth-child(2) {
animation-delay: 0.2s; /* Небольшая задержка */
}
.item:nth-child(3) {
animation-delay: 0.4s; /* Еще больше задержка */
}
Такой прием часто используют для «каскадных» появлений элементов списка или карточек.
animation-iteration-count
Определяет, сколько раз повторится анимация.
- Число (например
1,3,10). infinite— бесконечный цикл.
.loader {
animation: spin 1s linear infinite; /* Крутится постоянно */
}
@keyframes spin {
to {
transform: rotate(360deg); /* Поворот на полный круг */
}
}
Здесь достаточно указать только to, потому что from по умолчанию считается текущим состоянием.
animation-direction
Отвечает за направление движения по ключевым кадрам.
Основные значения:
normal— проигрывание от0%к100%.reverse— от100%к0%.alternate— сначала вперед, затем назад, затем снова вперед (чередование).alternate-reverse— сначала назад, затем вперед, и так по кругу.
Давайте разберемся с эффектом «маятника».
@keyframes move-left-right {
0% {
transform: translateX(0); /* Центр */
}
100% {
transform: translateX(50px); /* Сдвиг вправо */
}
}
.pendulum {
animation: move-left-right 1s ease-in-out infinite alternate;
/* alternate - туда и обратно */
}
Здесь анимация сначала идет к 100%, затем автоматически воспроизводится обратно к 0%, создавая туда-обратно движение без дополнительных ключевых кадров.
animation-fill-mode
Этот параметр определяет, как элемент выглядит до, во время и после анимации. Здесь многие новички часто путаются, поэтому разберем аккуратно.
Варианты:
none— анимация не влияет на стиль до начала и после окончания.forwards— после завершения анимации элемент остается в состоянии последнего ключевого кадра.backwards— до начала анимации (во время задержки) применяются стили из первого ключевого кадра.both— объединяетforwardsиbackwards.
Посмотрим пример с задержкой.
@keyframes appear {
from {
opacity: 0; /* Невидим в начале */
transform: scale(0.5);
}
to {
opacity: 1; /* Видим в конце */
transform: scale(1);
}
}
/* Вариант без fill-mode */
.box-none {
opacity: 0; /* Задаем вручную */
animation: appear 1s ease 2s; /* 2 секунды задержки */
}
/* Вариант с backwards */
.box-backwards {
animation: appear 1s ease 2s backwards;
/* В момент задержки сразу применяются стили from (opacity 0 scale 0.5) */
}
- Без
backwardsэлемент до начала анимации будет в том состоянии, которое задано обычными стилями (opacity: 0в примере). - С
backwardsстили начального ключевого кадра применяются во время задержки, и вы можете не дублировать их в основном селекторе.
forwards полезен, когда вы хотите сохранить финальное состояние, например, сделать появление элемента один раз без отката назад.
animation-play-state
Позволяет ставить анимацию на паузу и продолжать.
.box {
animation: move 3s linear infinite;
}
/* Класс для паузы */
.box.paused {
animation-play-state: paused; /* Останавливаем анимацию */
}
Этот прием часто используют в сочетании с JavaScript, но сам факт — свойство чисто CSS-ное. С помощью скрипта вы просто добавляете или удаляете класс paused.
Сокращенное свойство animation
Все перечисленные параметры можно объединить в одно свойство animation. Это облегчает чтение стилей и уменьшает их объем.
Порядок значений:
animation-nameanimation-durationanimation-timing-functionanimation-delayanimation-iteration-countanimation-directionanimation-fill-modeanimation-play-state
Не все обязательно указывать — эти значения частично опциональны, но порядок должен соблюдаться.
Пример:
.box {
animation: fade-in 1s ease-out 0.5s 3 alternate forwards running;
/* имя длит кривая задержка кол-во напр fill состояние */
}
На практике чаще ограничиваются 2–4 параметрами:
.box {
animation: fade-in 1s ease-out forwards;
}
Этого уже достаточно для множества задач.
Практические примеры CSS анимаций
Теперь давайте посмотрим на практику. Ниже несколько типичных эффектов, которые вы можете использовать в интерфейсе.
Плавное появление модального окна
@keyframes modal-show {
from {
opacity: 0;
transform: translateY(-20px); /* Сдвиг вверх */
}
to {
opacity: 1;
transform: translateY(0); /* Исходное положение */
}
}
.modal {
opacity: 0; /* По умолчанию скрыто */
transform: translateY(-20px); /* Начальный сдвиг */
animation: modal-show 0.4s ease-out forwards;
}
<div class="modal">
Контент модального окна
</div>
Комментарии показывают, что мы в ключевых кадрах описали и прозрачность, и смещение. forwards гарантирует, что после завершения анимации окно останется в видимом состоянии.
Анимация «подсветки» кнопки по наведению
@keyframes button-highlight {
0% {
box-shadow: 0 0 0 rgba(46, 204, 113, 0); /* Без тени */
}
50% {
box-shadow: 0 0 15px rgba(46, 204, 113, 0.7); /* Сильное свечение */
}
100% {
box-shadow: 0 0 0 rgba(46, 204, 113, 0); /* Возврат к нулю */
}
}
.button {
background-color: #2ecc71;
border: none;
color: #fff;
padding: 0.75rem 1.5rem;
cursor: pointer;
}
.button:hover {
animation: button-highlight 0.6s ease-out; /* Запускаем при наведении */
}
Эффект запускается только на время наведения, так как анимация связана с состоянием :hover.
Анимированная иконка «бургер-меню»
Смотрите, я покажу вам типичный пример, который часто встречается в реальных проектах.
.menu-icon {
width: 30px;
height: 20px;
position: relative;
cursor: pointer;
}
/* Общий стиль полосок */
.menu-icon span {
position: absolute;
left: 0;
right: 0;
height: 3px;
background: #333;
transition: transform 0.3s ease, top 0.3s ease, opacity 0.3s ease;
/* Используем transition для простоты */
}
/* Верхняя полоска */
.menu-icon span:nth-child(1) {
top: 0;
}
/* Средняя полоска */
.menu-icon span:nth-child(2) {
top: 50%;
transform: translateY(-50%);
}
/* Нижняя полоска */
.menu-icon span:nth-child(3) {
bottom: 0;
}
/* Состояние "открыто" */
.menu-icon.open span:nth-child(1) {
top: 50%; /* Смещаем к центру */
transform: translateY(-50%) rotate(45deg); /* Поворачиваем */
}
.menu-icon.open span:nth-child(2) {
opacity: 0; /* Прячем среднюю */
}
.menu-icon.open span:nth-child(3) {
bottom: auto;
top: 50%; /* Смещаем к центру */
transform: translateY(-50%) rotate(-45deg); /* Поворачиваем */
}
<div class="menu-icon">
<span></span>
<span></span>
<span></span>
</div>
// Пример простого JS - добавляем и убираем класс open по клику
document.querySelector('.menu-icon').addEventListener('click', function () {
// Переключаем класс open при каждом клике
this.classList.toggle('open');
});
Хотя здесь основное поведение реализовано с помощью transition, вполне возможно реализовать подобное поведение и через animation, если вам нужна более сложная последовательность шагов. Пример показывает, как анимации и переходы дополняют друг друга.
Прелоадер с вращающимся кругом
@keyframes loader-rotate {
to {
transform: rotate(360deg); /* Полный оборот */
}
}
.loader {
width: 40px;
height: 40px;
border: 4px solid #eee; /* Светлый круг */
border-top-color: #3498db; /* Верхняя часть синяя */
border-radius: 50%;
animation: loader-rotate 0.8s linear infinite;
}
<div class="loader"></div>
Этот эффект почти не требует ресурсов, так как основной стиль анимации завязан на transform, который хорошо оптимизируется браузерами.
Анимации и производительность
Когда вы начинаете активно использовать CSS анимации, важно учитывать их влияние на производительность, особенно на мобильных устройствах.
Старайтесь анимировать только transform и opacity
Причина в том, что изменения многих CSS-свойств заставляют браузер:
- Пересчитывать раскладку (layout).
- Перерисовывать (paint) элемент.
- Собирать слои и выполнять композицию (composite).
transform и opacity обычно затрагивают только этап композиции. Это самый дешевый этап рендера, который лучше всего оптимизируется и ускоряется GPU.
Примеры безопасных анимаций:
@keyframes safe-move {
from {
transform: translateX(0); /* Позиция */
}
to {
transform: translateX(100px);
}
}
@keyframes safe-fade {
from {
opacity: 0; /* Прозрачность */
}
to {
opacity: 1;
}
}
Нежелательные для частых анимаций свойства:
width,heightmargin,paddingtop,left,right,bottombox-shadow(особенно с большим размытием)border-radiusпри больших значениях
Иногда вам все равно придется анимировать их, но тогда стоит делать это реже или короче по времени.
Использование will-change
Свойство will-change подсказывает браузеру, какие свойства скоро будут изменены, и он может заранее оптимизировать эти элементы.
.card {
will-change: transform, opacity; /* Подсказываем браузеру о будущем изменении */
}
Используйте его:
- Для элементов с длительными или частыми анимациями.
- Для элементов, которые заранее известно, что будут анимированы (например, всплывающие блоки).
Не стоит ставить will-change «везде» — это может наоборот ухудшить производительность, потому что браузер будет держать лишние слои.
Предпочитайте CSS анимации вместо JS там, где возможно
CSS-анимации:
- Позволяют браузеру самостоятельно управлять частотой кадров.
- Могут быть вынесены на отдельный поток, не зависящий от главного JS-потока.
- Лучше оптимизируются на уровне движка.
JavaScript-анимации нужны, когда:
- Требуется сложная логика синхронизации с данными.
- Нужно подстраиваться под пользовательский ввод в реальном времени.
- Нужна тонкая ручная настройка каждого шага.
Обычно оптимальный подход — комбинировать: использовать CSS для чистой визуализации, а JS только для управления классами и состояниями.
Управление анимациями через классы и JS
Хотя статья о CSS, в реальных проектах почти всегда анимации запускаются и останавливаются с помощью JavaScript, который добавляет или убирает классы.
Запуск анимации по клику
@keyframes bounce {
0% {
transform: translateY(0); /* Старт */
}
30% {
transform: translateY(-20px); /* Прыжок вверх */
}
100% {
transform: translateY(0); /* Возврат вниз */
}
}
.box {
width: 100px;
height: 100px;
background: #e67e22;
cursor: pointer;
}
/* Класс, который добавляется при запуске */
.box.bouncing {
animation: bounce 0.6s ease; /* Один прыжок */
}
<div class="box"></div>
// На каждый клик добавляем класс bouncing
const box = document.querySelector('.box');
box.addEventListener('click', function () {
// Удаляем класс, чтобы анимация перезапустилась
this.classList.remove('bouncing');
// Принудительно перерассчитываем стиль
void this.offsetWidth; // Трюк для перезапуска анимации
// Снова добавляем класс
this.classList.add('bouncing');
});
Комментарии внутри показывают, почему нужно временно удалить класс и перерасчитать стили. Без этого анимация не перезапускается на повторном клике, так как браузер считает, что ничего не изменилось.
Остановка анимации
Если вам нужно полностью отключить анимацию, вы можете переопределить ее:
.box {
animation: spin 2s linear infinite;
}
/* Класс для отключения */
.box.no-animation {
animation: none; /* Полностью убираем анимацию */
}
// Пример переключения состояния
box.classList.add('no-animation'); // Отключить
box.classList.remove('no-animation'); // Включить
Этот подход полезен, если нужно, например, отключить «фановые» анимации по желанию пользователя.
Доступность и настройки пользователя (prefers-reduced-motion)
Не все пользователи хорошо переносят анимации. У некоторых они вызывают дискомфорт. Операционные системы позволяют указывать настройку «уменьшить движение», и вы можете ее учитывать.
Использование медиазапроса prefers-reduced-motion
@keyframes float {
0% {
transform: translateY(0); /* Старт */
}
50% {
transform: translateY(-10px); /* Подъем */
}
100% {
transform: translateY(0); /* Возврат */
}
}
.floating-element {
animation: float 3s ease-in-out infinite;
}
/* Если пользователь просит меньше движения */
@media (prefers-reduced-motion: reduce) {
.floating-element {
animation: none; /* Полностью отключаем анимацию */
}
}
Здесь я размещаю пример, чтобы вам было проще увидеть, как медиазапрос переопределяет анимацию. В реальных проектах вы можете вместо полного отключения сделать более короткую и мягкую анимацию.
@media (prefers-reduced-motion: reduce) {
.floating-element {
animation: float 1s ease-in-out 1;
}
}
Так эффект сохранится, но будет менее навязчивым.
Отладка и типичные ошибки
Анимация не запускается
Проверьте:
- Указано ли
animation-duration(по умолчанию 0s). - Совпадает ли
animation-nameс именем@keyframes. - Не стоит ли
animation-play-state: paused. - Не перекрывается ли анимация другим правилом (например, более специфичным селектором).
- Не забыли ли вы добавить нужный класс на элемент.
Пример проблемы с неправильным именем:
@keyframes fadeIn { /* Здесь с заглавной буквы */
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.box {
animation-name: fade-in; /* Здесь с дефисом - другое имя */
animation-duration: 1s;
}
В этом случае анимация вообще не запустится, потому что имена не совпадают. Исправьте на единый вариант.
Анимация перезапускается каждый раз при изменении класса
Если вы добавляете класс, который задает animation, и постоянно меняете другие классы того же элемента, анимация может перезапускаться. В таких ситуациях вынесите анимацию в отдельный класс и меняйте его только тогда, когда вам действительно нужно перезапустить поведение.
Конфликты с transition
Если одновременно на одно и то же свойство действуют и transition, и animation, поведение может стать непредсказуемым. Обычно стоит решать:
- Одно свойство — только
transitionили толькоanimation, не оба сразу; - Или стройте логику так, чтобы эффекты не пересекались по времени.
Например, анимировать позицию с помощью animation, а цвет — через transition.
Заключение
CSS анимации дают вам мощный инструмент для создания живых интерфейсов без сложного JavaScript-кода. Вы можете описывать последовательности состояний через @keyframes, управлять длительностью, повторением, задержкой, направлением и финальным состоянием элементов. Используя набор свойств animation-*, вы получаете тонкий контроль над всем жизненным циклом анимации.
Важно помнить о производительности и выбирать преимущественно анимацию transform и opacity, а также учитывать настройку prefers-reduced-motion, чтобы интерфейс оставался комфортным для всех пользователей. На практике CSS-анимации почти всегда используются в связке с классами и иногда с JS, который только переключает состояния.
Если вы будете строить анимации постепенно — от простых эффектов к более сложным последовательностям — и следить за тем, как они влияют на интерфейс, CSS анимации станут для вас понятным и удобным инструментом, а не источником хаоса в коде.
Частозадаваемые технические вопросы
Как запустить анимацию только один раз при первом появлении элемента и не повторять при последующих рендерах
Сделайте анимационный класс одноразовым. Например, при первом монтировании элемента добавьте класс animated-once, а после события animationend удалите из DOM сам элемент анимационной обертки или замените класс на «статический». В чистом CSS можно имитировать это через комбинацию animation-fill-mode: forwards и отсутствие изменений состояния (не переключать классы и не трогать DOM), чтобы анимация не перезапускалась.
Как перезапустить CSS анимацию без JavaScript только средствами CSS
Без JS напрямую перезапустить анимацию нельзя, но можно использовать разные состояния, например :hover, :focus, :checked у скрытого чекбокса или :target. Выносите анимацию в отдельный класс, который привязан к одному из этих состояний, и при каждом новом срабатывании состояния анимация будет стартовать заново. Типичный прием — скрытый чекбокс и :checked как триггер.
Как сделать, чтобы анимация начиналась не сразу после загрузки страницы, а только когда элемент появится в области видимости
В чистом CSS надежно это сделать нельзя, нужен JavaScript. Используйте Intersection Observer, чтобы отслеживать, когда элемент попадает в viewport, и тогда добавляйте класс, который задает animation. Алгоритм такой — наблюдаете за элементом, при isIntersecting true добавляете класс animate-in, прописываете для него нужную анимацию, а при необходимости отписываетесь от наблюдения, чтобы не запускать анимацию повторно.
Как синхронизировать несколько анимаций на разных элементах так, чтобы они начинались и заканчивались одновременно
Используйте одинаковые animation-duration и animation-delay для всех элементов, а также одинаковые значения animation-timing-function. Если анимации должны быть разными по ключевым кадрам, создайте несколько @keyframes, но следите, чтобы их временная шкала (проценты и количество циклов) совпадала. Для сложных сценариев удобнее использовать один общий класс запуска и единый момент добавления этого класса на все нужные элементы (через JS).
Как остановить анимацию в конкретный момент и сохранить текущее состояние элемента
Сделайте промежуточный класс, в котором прописано animation-play-state: paused. При его добавлении анимация останавливается на текущем кадре, потому что браузер просто перестает продвигать время анимации дальше. Если нужно остановить в строго определенный момент по времени, используйте JS — дождитесь нужного времени (таймер или контроль текущего прогресса через вычисления) и в этот момент добавьте класс с animation-play-state: paused.
Постройте личный план изучения Vue до уровня Middle — бесплатно!
Vue — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Vue
Лучшие курсы по теме

Vue 3 и Pinia
Антон Ларичев
TypeScript с нуля
Антон Ларичев