Содержание
CSS-анимации через @keyframes — это то, что делает веб-плакат живым. Элементы двигаются, пульсируют, вращаются, меняют цвет и форму без единой строки JavaScript. В этой статье разберём, как устроены keyframes, какие свойства управляют поведением анимации и как комбинировать несколько анимаций для создания сложных композиций.
Структура @keyframes
Анимация в CSS состоит из двух частей. Первая — блок @keyframes, который описывает, что именно происходит. Вторая — свойство animation на элементе, которое подключает этот блок и задаёт параметры воспроизведения.
Начнём с простейшего варианта: анимация от одного состояния к другому.
<div class="box"></div>
.box {
width: 50px;
height: 50px;
background: #FE3904;
position: absolute;
animation: slide 3s linear infinite alternate;
}
@keyframes slide {
from { left: 20px; }
to { left: calc(100% - 70px); }
}
/* from — начальное состояние.
to — конечное. alternate возвращает
обратно вместо резкого прыжка. */
Внутри @keyframes slide описаны два кадра: from (начальное состояние) и to (конечное). Браузер плавно интерполирует все промежуточные значения. Свойство animation на элементе связывает всё вместе: имя анимации (slide), длительность (3s), характер движения (linear), количество повторений (infinite) и направление (alternate — туда-обратно).
Вместо from и to можно использовать процентные значения, что даёт больше контроля. Проценты позволяют задать сколько угодно промежуточных точек.
<div class="box"></div>
@keyframes zigzag {
0% { left: 20px; top: 20px; }
25% { left: calc(100% - 70px); top: 20px; }
50% { left: calc(100% - 70px); top: calc(100% - 70px); }
75% { left: 20px; top: calc(100% - 70px); }
100% { left: 20px; top: 20px; }
}
/* Проценты позволяют задать
промежуточные точки.
0% и 100% совпадают —
цикл бесшовный. */
Здесь четыре промежуточные точки: 0%, 25%, 50%, 75% и 100%. Элемент проходит по квадратной траектории: правый верхний угол, правый нижний, левый нижний и обратно в начало. Значения 0% и 100% совпадают, поэтому цикл бесшовный — нет рывка при переходе к следующей итерации.
from — это то же самое, что 0%. Для to — то же, что 100%. Для двухкадровых анимаций from/to читается проще. Для многокадровых используйте проценты
Свойства animation
Свойство animation — это сокращённая запись (shorthand), которая объединяет несколько отдельных свойств. Давайте разберём каждое.
animation-duration — длительность одного цикла анимации. Определяет скорость и, что важнее, характер.
<div class="track"><div class="runner fast"></div></div>
<div class="track"><div class="runner medium"></div></div>
<div class="track"><div class="runner slow"></div></div>
.fast { animation: run 1s linear infinite alternate; }
.medium { animation: run 3s linear infinite alternate; }
.slow { animation: run 6s linear infinite alternate; }
/* Одна и та же анимация,
три разных duration.
1s — нервозно, 3s — спокойно,
6s — медитативно. */
Одна и та же анимация с тремя разными duration. Красный пробегает за 1 секунду, зелёный — за 3, голубой — за 6. Визуальный характер совершенно разный, хотя траектория одинаковая.
animation-delay — задержка перед первым запуском. Полезна для создания каскадных эффектов, когда одна и та же анимация запускается на нескольких элементах с небольшим сдвигом.
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
.dot {
animation: pop 1.2s ease-in-out infinite;
}
.dot:nth-child(1) { animation-delay: 0s; }
.dot:nth-child(2) { animation-delay: 0.2s; }
.dot:nth-child(3) { animation-delay: 0.4s; }
.dot:nth-child(4) { animation-delay: 0.6s; }
.dot:nth-child(5) { animation-delay: 0.8s; }
@keyframes pop {
0%, 100% { transform: scale(1); opacity: 0.4; }
50% { transform: scale(1.5); opacity: 1; }
}
/* Одна анимация, разные delay —
получается волна. */
Пять кругов, одна анимация, разные delay с шагом 0.2 секунды. Получается волна, бегущая слева направо. Это один из самых простых и в то же время эффектных приёмов для веб-плаката.
animation-direction — направление воспроизведения. Четыре варианта.
<div class="mover dir-normal"></div>
<div class="mover dir-reverse"></div>
<div class="mover dir-alternate"></div>
<div class="mover dir-alt-rev"></div>
.dir-normal { animation-direction: normal; }
.dir-reverse { animation-direction: reverse; }
.dir-alternate { animation-direction: alternate; }
.dir-alt-rev { animation-direction: alternate-reverse; }
/* normal — от from к to, прыжок назад.
reverse — от to к from.
alternate — туда-обратно.
alternate-reverse — обратно-туда. */
normal — от from к to, потом резкий прыжок в начало. reverse — от to к from. alternate — туда-обратно, без рывков. alternate-reverse — обратно-туда. Для бесконечных анимаций alternate почти всегда лучший выбор, потому что он исключает резкий сброс в начальное состояние.
animation-iteration-count — количество повторений. infinite — бесконечно. Число например, 3 — конечное количество циклов. Для декоративных анимаций в веб-плакате вы почти всегда будете использовать infinite.
animation-fill-mode — что происходит с элементом до и после анимации. Это свойство имеет значение только для конечных анимаций не infinite.
<div class="fill-none">...</div>
<div class="fill-forwards">...</div>
/* animation-fill-mode: none (по умолчанию)
После завершения элемент прыгает
в исходное состояние */
.fill-none {
animation: grow 2s ease 1s 1 normal none;
}
/* forwards — элемент остаётся
в конечном состоянии */
.fill-forwards {
animation: grow 2s ease 1s 1 normal forwards;
}
@keyframes grow {
to { transform: scale(1.4); background: #9EFF70; }
}
none по умолчанию — после завершения элемент прыгает в исходное состояние, как будто анимации не было. forwards — элемент остаётся в конечном состоянии. Третий блок использует infinite, поэтому fill-mode ему не нужен, он просто крутится бесконечно.
Пауза анимации
Отдельного внимания заслуживает свойство animation-play-state. Оно принимает два значения: running по умолчанию и paused. При переключении в paused анимация замораживается в текущей точке. Не сбрасывается, не перезапускается, а именно встаёт на паузу. При возврате к running продолжается с того же места.
<div class="orbit-ring">
<div class="planet planet-1"></div>
<div class="planet planet-2"></div>
<div class="planet planet-3"></div>
</div>
.orbit-ring {
animation: spin 6s linear infinite;
}
.container:hover .orbit-ring {
animation-play-state: paused;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* paused замораживает анимацию
в текущей точке.
При уходе курсора — продолжение
с того же места. Не сброс. */
Три планеты вращаются вокруг центра. Наведите курсор и орбита замрёт. Уберите и продолжает с того же угла. Это реализуется одной строкой с ховером
Для веб-плаката animation-play-state полезен в двух случаях. Первый — дать зрителю возможность остановить движение, чтобы рассмотреть детали. Второй — обратная логика: запустить анимацию только при наведении. В таком случае базовое состояние — paused, а при hover — running.
animation-play-state: paused замораживает все текущие анимации элемента, включая составные. Если на элементе две анимации через запятую, пауза остановит обе. Нельзя поставить на паузу одну из двух анимаций на одном элементе.
Несколько свойств keyframe
Внутри каждого кадра 0%, 25%, to и так далее можно менять сколько угодно CSS-свойств одновременно. Браузер будет интерполировать каждое из них параллельно.
<div class="morph"></div>
@keyframes morph {
0% {
transform: scale(1) rotate(0deg);
border-radius: 8px;
background: #FE3904;
}
25% {
transform: scale(1.3) rotate(90deg);
border-radius: 50%;
background: #9EFF70;
}
50% {
transform: scale(0.8) rotate(180deg);
border-radius: 8px;
background: #7ADFF1;
}
/* В одном keyframe можно менять
сколько угодно свойств одновременно */
}
Здесь в одном keyframe одновременно меняются transform, border-radius от квадрата к кругу и обратно и background смена цвета. Четыре кадра — четыре состояния, и все переходы происходят плавно.
Две анимации на одном элементе
Иногда удобнее разделить движение на две независимые анимации, каждая со своим ритмом. Для этого в свойстве animation перечисляются две анимации через запятую.
<div class="dual"></div>
.dual {
animation:
run 3s linear infinite alternate,
color-shift 2s ease-in-out infinite;
}
@keyframes run {
from { left: 0; }
to { left: calc(100% - 50px); }
}
@keyframes color-shift {
0%, 100% { background: #FE3904; border-radius: 8px; }
50% { background: #9EFF70; border-radius: 50%; }
}
/* Две анимации через запятую.
Каждая со своим duration и timing.
Работают параллельно, не мешая
друг другу. */
Элемент двигается слева направо анимация run, 3 секунды, linear и одновременно меняет цвет и форму анимация color-shift, 2 секунды, ease-in-out. Два ритма наложены друг на друга и создают сложный визуальный рисунок, хотя каждая анимация по отдельности простая.
Это тот же принцип, который мы видели в спрайтовой анимации Flying Toasters: одна анимация отвечает за покадровое переключение, другая — за перемещение.
Каскады и бегущие строки
Два приёма, которые часто встречаются в веб-плакатах.
Каскад — одна анимация на нескольких элементах с нарастающим delay.
<div class="bar"></div>
<div class="bar"></div>
<!-- ...повторить 10 раз -->
.bar {
width: 24px;
background: #FE3904;
animation: bar 1.4s ease-in-out infinite alternate;
}
.bar:nth-child(1) { animation-delay: 0s; }
.bar:nth-child(2) { animation-delay: 0.1s; }
.bar:nth-child(3) { animation-delay: 0.2s; }
/* ...и так далее с шагом 0.1s */
@keyframes bar {
from { height: 20px; }
to { height: 180px; }
}
/* Эквалайзер. Одна анимация,
10 элементов, сдвиг по 0.1s.
Простейший способ создать
волнообразное движение. */
Десять полосок, одна анимация bar, задержка увеличивается на 0.1 секунды с каждым элементом. Получается эквалайзер. Этот паттерн можно применить к чему угодно: буквам в заголовке, карточкам, декоративным фигурам.
Бегущая строка — текст или элементы бесконечно сдвигаются за пределы экрана.
<div class="marquee-track">
<span>ВЕБ-ПЛАКАТ</span>
<span>•</span>
<span>ШКОЛА ДИЗАЙНА</span>
<!-- Текст дублируется для бесшовности -->
</div>
.marquee-track {
display: flex;
gap: 40px;
white-space: nowrap;
animation: marquee 8s linear infinite;
}
@keyframes marquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
/* Сдвигаем на -50% — ровно половину
дублированного контента.
Когда первая копия уехала,
вторая встала на её место.
Цикл бесшовный. */
Хитрость в том, что содержимое дублируется. Анимация сдвигает весь блок на -50%. Когда первая копия текста уезжает влево, вторая оказывается ровно на том месте, где была первая. Цикл бесшовный. Этот приём используется во множестве студенческих веб-плакатов для создания фоновых текстовых лент.
Что можно и нельзя анимировать
Не все CSS-свойства поддаются плавной анимации. Это важно знать, чтобы не тратить время на отладку того, что просто не работает.
<div class="can-yes">Анимируется</div>
<div class="can-no">display ✗</div>
/* Можно анимировать: */
/* transform, opacity, background,
color, width, height, margin,
padding, border-radius,
fill, stroke, stroke-dashoffset */
/* Нельзя плавно анимировать: */
/* display, font-family, position,
grid-template-columns, content */
/* Лучше всего: transform и opacity.
Они не вызывают перерисовку
макета (reflow) и работают
на GPU — самые плавные. */
Хорошо анимируются transform (translate, rotate, scale), opacity, background-color, color, width, height, padding, margin, border-radius, а также SVG-свойства fill, stroke и stroke-dashoffset.
Не анимируются плавно display. Элемент не может плавно появляться через display: none → block, font-family нельзя интерполировать между шрифтами, position, grid-template-columns, content.
Среди анимируемых свойств есть два «чемпиона» по производительности: transform и opacity. Они обрабатываются на GPU и не вызывают перерасчёт макета reflow. Если у вас есть выбор между анимацией left и transform: translateX, второй вариант будет работать значительно плавнее, особенно на мобильных устройствах.
Правило для производительных анимаций: двигайте элементы через transform: translate(), масштабируйте через transform: scale(), вращайте через transform: rotate(), показывайте и скрывайте через opacity. Если вам нужно анимировать width или height, попробуйте заменить это на transform: scale(). Результат визуально похож, но в разы плавнее.




