Движение

Добавляем статичным объектам выразительность с помощью базовых анимаций

3 минуты чтения

Содержание

О модуле

Веб-плакат живёт во времени. В отличие от печатного плаката, где динамика передаётся через композицию и экспрессию формы, здесь движение — буквальное. Элементы вращаются, смещаются, пульсируют, дрейфуют по экрану. CSS-анимации через @keyframes дают дизайнеру контроль над тем, как и когда зритель воспринимает визуальное сообщение. Замедлить, ускорить, зациклить — всё это становится частью авторского инструментария.

Линейное движение

Объект едет из точки A в точку B по прямой. Туда — обратно, туда — обратно. Или просто туда и исчезает. Это самый базовый тип движения, и начинать стоит именно с него — чтобы почувствовать, как вообще работает связка @keyframes + animation

Технически всё просто: вы описываете два состояния — начальное и конечное, — а браузер интерполирует переход между ними. Свойство animation-direction: alternate заставляет объект возвращаться обратно, создавая маятниковый эффект. А infinite зацикливает движение, чтобы анимация не останавливалась.

Объяснение

Обратите внимание на calc(100% - 80px) — это способ сделать так, чтобы объект не вылезал за границы контейнера. Функция calc() вычитает ширину самого элемента из ширины родителя, и квадрат аккуратно доезжает до правого края. Без этого приёма объект частично уедет за пределы видимой области.


<div class="demo-cube"></div>
      

.demo-cube {
  position: absolute;
  animation: movement-linear 5s linear infinite alternate;
}

@keyframes movement-linear {
  from {
    left: 0;
  }
  to {
    left: calc(100% - 40px);
  }
}
/* Вычитаем ширину 40 px объекта для корректной анимации */
      

Колебательное движение

Представьте маятник настенных часов или качели. Объект раскачивается вокруг точки опоры — медленно в крайних положениях, быстрее в центре. Этот характер движения ощущается естественным, потому что мы видим его в физическом мире постоянно.

Секрет колебательного движения — в свойстве transform-origin. По умолчанию вращение через rotate происходит вокруг центра элемента, что выглядит как кручение на месте. Стоит сместить точку трансформации — скажем, в верхнюю часть объекта — и вращение превращается в качание, словно объект подвешен на нитке.


<div class="demo-pendulum">
  <div class="demo-pendulum-ball"></div>
</div>

@keyframes movement-pendulum {
  from {
    transform: rotate(-25deg);
  }
  to {
    transform: rotate(25deg);
  }
}

.demo-pendulum {
position: absolute;
top: 10%;
left: 50%;
width: 6px;
height: 180px;
background: rgba(255, 255, 255, 0.3);

/* Точка опоры — верхний центр */
transform-origin: top center;
animation: movement-pendulum 2s ease-in-out infinite alternate;
}

.demo-pendulum-ball {
position: absolute;
bottom: -20px;
left: 50%;
width: 40px;
height: 40px;
border-radius: 50%;
background: #fff;
transform: translateX(-50%);
}
            
          

Частая ошибка — забыть про transform-origin. Без его настройки rotate вращает объект вокруг собственного центра, и вместо маятника вы получаете вертушку. Если вам нужно качание — всегда задавайте точку опоры явно.

Пульсация

Объект ритмично увеличивается и уменьшается — как будто дышит. Пульсация отлично работает для привлечения внимания к конкретному элементу: кнопке, логотипу, точке на карте. При этом она достаточно мягкая, чтобы не раздражать, особенно если использовать плавный тайминг и умеренную амплитуду.

Реализуется через transform: scale() Важно понимать разницу между пульсацией и «морганием»: пульсация — это масштабирование, а моргание — это смена прозрачности через opacity Кайфовое решение: комбинировать оба свойства. Когда объект одновременно увеличивается и становится чуть прозрачнее на пике, эффект выглядит живее.


<div class="demo-pulse-circle"></div>
      

@keyframes movement-pulse {
  0% {
    transform: scale(1);
    opacity: 1;
  }
  50% {
    transform: scale(1.3);
    opacity: 0.7;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

.demo-pulse-circle {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 80px;
  height: 80px;
  border-radius: 50%;
  background: #fff;
  transform: translate(-50%, -50%);
  animation: movement-pulse 2s ease-in-out infinite;
}
      

Дрифт

Мягкое, почти незаметное перемещение — как если бы объект плыл в невесомости. Дрифт работает в медитативных, атмосферных композициях и часто используется для фоновых слоёв: декоративных фигур, текстур, размытых пятен.

Весь фокус в длительности и комбинировании. Одна анимация сдвигает объект по оси X, другая — по оси Y, третья — немного вращает. У каждой своя длительность, и так как эти длительности не кратны друг другу, суммарная траектория получается сложной и непредсказуемой. Зритель не может «считать» паттерн повторения — движение кажется хаотичным, хотя каждая ось строго предсказуема.


<div class="demo-drift-wrapper-x">
  <div class="demo-drift-wrapper-y">
    <div class="demo-drift-element"></div>
  </div>
</div>

@keyframes drift-x {
  0%, 100% { transform: translateX(0); }
  50% { transform: translateX(80px); }
}

@keyframes drift-y {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-60px); }
}

@keyframes drift-rotate {
  0%, 100% { transform: rotate(0deg); }
  50% { transform: rotate(15deg); }
}

/* Обёртки для наложения нескольких transform-анимаций */
.demo-drift-wrapper-x {
  position: absolute;
  top: 40%;
  left: 35%;
  animation: drift-x 23s ease-in-out infinite;
}

.demo-drift-wrapper-y {
  animation: drift-y 17s ease-in-out infinite;
}

.demo-drift-element {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.6);
  animation: drift-rotate 31s ease-in-out infinite;
}
            
          

Попробуйте задать длительности вроде 23s, 17s, 31s — простые числа гарантируют, что циклы совпадут нескоро.

Здесь важен технический момент. CSS не позволяет применить несколько @keyframes к одному свойству transform на одном элементе — последнее перезапишет предыдущее. Поэтому используется трюк с вложенными обёртками: каждый уровень вложенности отвечает за свою ось движения. Внешний div двигает по X, средний — по Y, внутренний — вращает. Вместе они создают сложную траекторию из простых компонентов.

Если вы попытаетесь записать несколько transform анимаций на один элемент через запятую в свойстве animation, анимации запустятся, но каждый @keyframes перезапишет значение transform предыдущего.

В результате вы увидите только последнюю анимацию. Решение — вложенные обёртки, по одной на каждую ось.

Эластичное движение

Объект пролетает целевую точку и возвращается назад — как пружина или резинка. Подпрыгивает, чуть проскакивает, ещё раз подпрыгивает, и постепенно затухает. Это придаёт анимации характер, энергию, игривость — что-то среднее между мультяшным и физическим.

Реализуется через многошаговый @keyframes, в котором объект «перелетает» через целевую точку несколько раз с уменьшающейся амплитудой. Вы задаёте промежуточные кадры вручную: допустим, элемент должен приземлиться на translateY(0), но по пути проходит через translateY(-60px), потом translateY(10px), потом translateY(-5px), и только потом останавливается.

Есть альтернатива — кастомная функция cubic-bezier(), которая позволяет создать эффект перелёта для простых переходов. Но для сложных, многофазных пружинных колебаний она не подходит — нужны ручные ключевые кадры. Генератор cubic-bezier


<div class="demo-elastic-ball"></div>

@keyframes movement-elastic {
  0% {
    transform: translateY(-100px);
  }
  /* проскочил вниз */
  30% {
    transform: translateY(20px);   
  }
  /* подпрыгнул обратно */
  50% {
    transform: translateY(-10px);  
  }
  /* ещё раз проскочил */
  65% {
    transform: translateY(5px);    
  }
  /* почти затух */
  80% {
    transform: translateY(-2px);   
  }
  /* замер */
  100% {
    transform: translateY(0);      
  }
}

.demo-elastic-ball {
  position: absolute;
  top: 55%;
  left: 50%;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: #fff;
  transform: translateX(-50%);
  animation: movement-elastic 1.5s ease-out infinite;
}
            
          

Орбитальное движение

Объект движется по кругу или эллипсу — как планета вокруг звезды. Выглядит эффектно, особенно когда несколько элементов вращаются с разной скоростью. Встречается в веб-плакатах с космической, научной или абстрактной тематикой.

Самый удобный способ реализации — анимировать не сам объект, а его обёртку. Представьте невидимый рычаг, который вращается вокруг центра. На конце рычага закреплён объект. Рычаг крутится — объект описывает окружность. Технически это rotate на родительском контейнере, а дочерний элемент просто смещён от центра через translate

Есть нюанс: если на вращающемся контейнере лежит текст или изображение, оно тоже будет вращаться — и станет нечитаемым. Чтобы это исправить, на дочерний элемент навешивается контр-вращение: такое же rotate, но в обратную сторону и с той же скоростью. Объект движется по орбите, но сохраняет свою ориентацию.


<div class="demo-drift-wrapper-x">
  <div class="demo-drift-wrapper-y">
    <div class="demo-drift-element"></div>
  </div>
</div>

@keyframes orbit-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

@keyframes orbit-counter-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(-360deg); }
}

/* Невидимый рычаг, вращающийся вокруг центра */
.demo-orbit-arm {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  animation: orbit-spin 6s linear infinite;
}

/* Объект, смещённый от центра вращения */
.demo-orbit-planet {
  position: absolute;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: #fff;
  /* Смещение задаёт радиус орбиты */
  top: -120px;
  left: -20px;
  /* Контр-вращение сохраняет ориентацию */
  animation: orbit-counter-spin 6s linear infinite;
}
            
          

Для эллиптической орбиты добавьте transform: scaleY(0.5) на обёртку всей системы — круговое вращение сплющится в эллипс. Это проще, чем выстраивать эллиптическую траекторию через translateX и translateY с разными амплитудами.

Псведослучайное движение

Это один из самых выразительных паттернов для веб-плакатов, и при этом — обманчиво простой. Принцип такой: несколько одинаковых объектов получают одну и ту же анимацию, но с разными animation-duration и animation-delay. Из-за рассинхронизации зрителю кажется, что объекты двигаются хаотично, хотя каждый из них следует строго предсказуемой траектории.

Чем больше объектов — тем убедительнее иллюзия случайности. Если у восьми элементов длительности от 3s до 7s с шагом 0.5s, их паттерны совпадут заново очень нескоро, и глаз просто не успевает считать повторения. Это работает примерно как полиритмы в музыке: каждый голос прост, но вместе они создают сложную фактуру.


<div class="demo-pr-dot"></div>
<div class="demo-pr-dot"></div>
<div class="demo-pr-dot"></div>
<div class="demo-pr-dot"></div>
<div class="demo-pr-dot"></div>
<div class="demo-pr-dot"></div>
<div class="demo-pr-dot"></div>
<div class="demo-pr-dot"></div>

@keyframes pseudo-random-move {
  0% { transform: translate(0, 0); }
  25% { transform: translate(100px, -50px); }
  50% { transform: translate(-30px, 80px); }
  75% { transform: translate(60px, 20px); }
  100% { transform: translate(0, 0); }
}

.demo-pr-dot {
  position: absolute;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #fff;
  animation: pseudo-random-move ease-in-out infinite alternate;
}

/* Каждый элемент получает свою длительность и позицию */
.demo-pr-dot:nth-child(1) { top: 20%; left: 10%; animation-duration: 4s; }
.demo-pr-dot:nth-child(2) { top: 60%; left: 25%; animation-duration: 5.5s; animation-delay: -1s; }
.demo-pr-dot:nth-child(3) { top: 35%; left: 55%; animation-duration: 3.5s; animation-delay: -2.5s; }
.demo-pr-dot:nth-child(4) { top: 75%; left: 70%; animation-duration: 6s; animation-delay: -0.5s; }
.demo-pr-dot:nth-child(5) { top: 15%; left: 80%; animation-duration: 4.5s; animation-delay: -3s; }
.demo-pr-dot:nth-child(6) { top: 50%; left: 45%; animation-duration: 7s; animation-delay: -1.5s; }
.demo-pr-dot:nth-child(7) { top: 80%; left: 15%; animation-duration: 5s; animation-delay: -4s; }
.demo-pr-dot:nth-child(8) { top: 40%; left: 90%; animation-duration: 3s; animation-delay: -2s; }
            
          

Обратите внимание на отрицательные animation-delay — это позволяет прокрутить анимацию в прошлое, чтобы при загрузке страницы объекты стартовали не из одной точки. Без этого приёма в первую секунду все точки синхронно дёрнутся в одну сторону, и иллюзия случайности сломается.

Попробуйте использовать не только разные длительности, но и разную прозрачность, размер, или даже слегка разные траектории через отдельные @keyframes. Чем больше параметров варьируется, тем натуральнее выглядит результат.