Анимация — несколько рисунков, показанных последовательно, чтобы создать иллюзию движения. Анимации привлекают внимание пользователей и помогают сделать интерфейсы понятными. Мы подготовили перевод статьи, чтобы разобраться, как контролировать движения и переходы в CSS.

Есть два способа анимировать элементы в CSS: свойства animationи transition.

Свойство animationпозволяет изменять свойства элемента в течение определённого периода, а transitionопределяет, как элемент меняется за определённый период.

Возникает вопрос, в чём же разница. Для animation нужны @keyframes, то есть требуется определить точки начала и конца изменений. Ключевые кадры используют для пошаговых анимаций. Более простые анимации создают при помощи transition. В этом случае движение запускается по определённому сигналу, например, по клику или наведению курсора. Свойства animation и transition управляют продолжительностью, задержкой, итерациями движения.

Что такое временные функции

Это функции, которые определяют, как веб-элементы работают в каждом кадре анимации: animation-timing-function и transition-timing-function действуют и как автономные свойства CSS, и как значения свойств animation и transition .

Поэтому свойство animation может выглядеть так:

animation: <name> <duration> <timing-function>;

Или так:

animation-name: name;
animation-duration: 500ms;
animation-timing-function: ease;

То же самое со свойством transition:

transition: <property> <duration> <timing-function>;

Или:

transition-property: transform;
transition-duration: 500ms;
transition-timing-function: ease;

Transition-timing-function

Transition-timing-function определяет кривую скорости эффекта перехода. Кривая — соединение множества точек. Так что каждый период transition разделён на несколько точек.

Всего в CSS шесть transition-timing-functions:

  1. linear

  2. ease

  3. ease-in

  4. ease-out

  5. ease-in-out

  6. cubic-bezier()

Они служат для создания плавных переходов, поэтому называются функциями плавности.

Функция linear 

Анимации с linear двигаются с постоянной скоростью. При этом с начала до конца никаких изменений внутри функции нет, так что кривая скорости тут, скорее, прямая.

Функция ease 

Анимации с этой функцией начинаются медленно, ускоряются, потом снова замедляются до стартовых значений. Эта функция используется по умолчанию.

Для понимания устроим гонки, участвуют ease и linear:

Используем простую разметку  HTML:

<body>
  <div class="container">
    <div class="rockets rocket-1"><img src="Rocket1.png"></div>
    <div class="rockets rocket-2"><img src="Rocket2.png"></div>
  </div>
</body>

Добавляем CSS:

body {
  margin: 0px;
  padding: 0px;
}
*{
  box-sizing: border-box;
}
.container{
  width: 100%;
  height: 300px;
  background: rgba(224,214,233, 0.5)
}
.rockets{
  width: 500px;
  height: 100px;
  transition-duration: 2s;
  transition-property: transform;
  display: flex;
  align-items: center;
}
.rockets img{
  height: 100px;
  border-right: 1px solid red;/*To track the speed easily*/
}

Добавляем анимацию. Триггером делаем :hover.

.container:hover .rockets{
  transform: translateX(500px);
}
.rocket-1{
  transition-timing-function: linear;
}
.rocket-2{
  transition-timing-function: ease;
}

Итог:

На первый взгляд, кажется, что ease быстрее, чем linear, но в обоих случаях animation-duration одинаково. Если присмотреться, обе анимации заканчиваются в одной точке. В гонке ничья.

Функции ease-in, ease-out и ease-in-out 

Анимации с функцией ease-in начинаются медленно, ускоряются к концу. Функция ease-out вызывает обратный эффект: быстрый старт и медленный финал.

Ease-in-out анимания начинается медленно, ускоряется в середине, завершается медленно. Похоже на ease, но только с симметричной кривой скорости.

Снова гоночки:

.rocket-1{
  transition-timing-function: ease-in;
}
.rocket-2{
  transition-timing-function: ease-out;
}
.rocket-3{
  transition-timing-function: ease-in-out
}

Функция cubic-bezier()

Все функции плавности основаны на кубической кривой Безье, которая определяется контрольными точками. Даже linear  — кривая Безье с двумя контрольными точками.

cubic-bezier(P1,P2,P3,P4)

Точки P1 и P3 должны быть в пределах от 0 до 1. Точки P2 и P4 могут быть с любыми значениями, в том числе отрицательными. Удобнее создавать все точки в пределах от -1 до 1, чтобы анимации не дёргались.

Зададим рандомные значения для точек.

.rocket-1{
  transition-timing-function: cubic-bezier(.66,.39,.21,.67);
}
.rocket-2{
  transition-timing-function: cubic-bezier(1,-0.42,.42,-0.39);
}
.rocket-3{
  transition-timing-function: cubic-bezier(.57,1.34,.21,0);
}

Наводим курсор и смотрим, что вышло.

Значения для контрольных точек можно задавать вручную, но это довольно долго. Есть два способа выбрать идеальную скорость cubic-bezier(): инструменты разработчика и генератор. 

Используем инструменты проверки

Задаём анимированному элементу любую временную функцию. Открываем инструменты разработчика. У нас скриншоты из Chrome.

Если вы используете сокращённые свойства animation или transition, рядом с названием свойства найдёте значок раскрывающегося списка. Кликаем, раскрываем список значений — среди них будет время. В другом случае это свойство отображается отдельно. 

Рядом с названием временно функции есть значок кривой, который открывает редактор Безье. 

Кликаем и регулируем cubic-bezier(). В этом помогает визуализатор.

Когда найдёте подходящую кривую скорости, скопируйте cubic-bezier() и вставьте в проект.

Используем генератор

Заходим на cubic-bezier.com — это инструмент, которые создаёт кривые скорости. Поиграйте с настройками, пока не найдёте подходящий вариант. В это помогут предварительный просмотр и сравнение с функциями по умолчанию. Скопируйте и вставьте в проект.

Animation-timing-function

Функция animation-timing-function определяет кривую скорости. Да, тоже. Свойство  animation делится на @keyframes, которые работают как FPS у камер. Animation-timing-function работает с любой функцией плавности, а также с другими функциями: step-end, step-start и steps.

Когда со свойством animation используют функция плавности, нужно добавлять  @keyframes с начальной и конечной точками. Посмотрим, как это работает. Сделаем анимации скролла, элементы будут появляться при прокрутке вниз.

Начинаем:

<section class="container">
     <h2>Ease-in, Ease-out, and Ease-in-out</h2>
        <div class="text-container">
          <div class="text-box reveal box-3">
            <h3>Ease-in</h3>
            <p>Random text</p>
          </div>
          <div class="text-box reveal box-4">
            <h3>Ease-out</h3>
            <p>Random text</p>
          </div>
          <div class="text-box reveal box-5">
            <h3>Ease-in-out</h3>
            <p>Random text</p>
          </div>
        </div>
</section>

Стилизуем и присваиваем каждому элементу animation:

.active.box-1 {
  animation: box-1 1s ease;
}
.active.box-2 {
  animation: box-2 1s linear;
}
.active.box-3 {
  animation: box-3 1s ease-in;
}
.active.box-4 {
  animation: box-4 1s ease-out;
}
.active.box-5 {
  animation: box-5 1s ease-in-out;
}
.active.box-6 {
  animation: box-6 1s cubic-bezier(.66,.39,.21,.67);
}
.active.box-7 {
  animation: box-7 1s cubic-bezier(1,-0.42,.42,-0.39);
}
.active.box-8 {
  animation: box-8 1s cubic-bezier(.57,1.34,.21,0);
}

Все анимированные элементы сопровождаем классом reveal, который спрячет их до запуска анимации.

.reveal {
  position: relative;
  opacity: 0;
}
.reveal.active {
  opacity: 1;
}

Анимации прокрутки контролируем функцией JavaScript:

 function reveal() {
        var reveals = document.querySelectorAll(".reveal");
        for (var i = 0; i < reveals.length; i++) {
          var windowHeight = window.innerHeight;
          var elementTop = reveals[i].getBoundingClientRect().top;
          var elementVisible = 150;
          if (elementTop < windowHeight - elementVisible) {
            reveals[i].classList.add("active");
          } else {
            reveals[i].classList.remove("active");
          }
        }
      }
      window.addEventListener("scroll", reveal);

Условие запуска анимации — появление элемента при скролле страницы. Эта функция отслеживает дистанцию прокрутки элемента, elementTop, пока скролл не достигнет точки, в которой элемент должен быть видимым, elementVisible.

getBoundingClientRect().top — расстояние от верхней точки области просмотра, window.innerHeight — высота области просмотра.

Далее@keyframes:

@keyframes box-1 {
  0% {
    transform: translateY(100px);
    opacity: 0;
  }
  100% {
    transform: translateY(0);
    opacity: 1;
  }
}
@keyframes box-2 {
  0% {
    transform: translateY(100px);
    opacity: 0;
  }
  100% {
    transform: translateY(0);
    opacity: 1;
  }
}
@keyframes box-3 {
  0% {
    transform: translateY(100px);
    opacity: 0;
  }
  100% {
    transform: translateY(0);
    opacity: 1;
  }
}
@keyframes box-4 {
  0% {
    transform: translateY(100px);
    opacity: 0;
  }
  100% {
    transform: translateY(0);
    opacity: 1;
  }
}
@keyframes box-5 {
  0% {
    transform: translateY(100px);
    opacity: 0;
  }
  100% {
    transform: translateY(0);
    opacity: 1;
  }
}
@keyframes box-6 {
  0% {
    transform: translateY(100px);
    opacity: 0;
  }
  100% {
    transform: translateY(0);
    opacity: 1;
  }
}
@keyframes box-7 {
  0% {
    transform: translateY(100px);
    opacity: 0;
  }
  100% {
    transform: translateY(0);
    opacity: 1;
  }
}
@keyframes box-8 {
  0% {
    transform: translateY(100px);
    opacity: 0;
  }
  100% {
    transform: translateY(0);
    opacity: 1;
  }
}

Функции step-end, step-start и steps()

Эти функции разбивают анимацию на равные части или шаги: step-end запускает анимацию после первого @keyframe и пропускает шаг в конце, step-start действует наоборот. Вот как это работает:

.rockets{
  width: 500px;
  height: 100px;
  animation-duration: 2s;
  animation-name: flight;
  animation-iteration-count: infinite;
  animation-direction: alternate-reverse;
  display: flex;
  align-items: center;
}
.rocket-1{
  animation-timing-function: step-end;
}
.rocket-2{
  animation-timing-function: step-start;
}
@keyframes flight{
  0%{transform: none;}
  25%{transform: translateX(125px);}
  50%{transform: translateX(250px);}
  75%{transform: translateX(375px);}
  100%{transform: translateX(500px);}
}

Итог:

Ракета с step-start переходит к первому @keyframe, как только начинается анимация. 

Для усложнения используем steps(), чтобы указать количество шагов в анимации:

.rocket-1{
  animation-timing-function: steps(5);
}
.rocket-2{
  animation-timing-function: steps(10);
}
.rocket-3{
  animation-timing-function: steps(20);
}
@keyframes flight{
  0%{transform: none;}
  100%{transform: translateX(500px);}
}

Итог:

Ключевые слова, которые можно использовать со steps() в дополнение к количеству шагов:

  1. jump-start

  2. jump-end

  3. jump-both

  4. jump-none

Jump-start и jump-end работают так же, как step-start и step-end; jump-both — анимация пропускает шаг с обоих концов; jump-none— анимация не пропускает ни одного шага, каждый шаг равномерно распределён по продолжительности.

.rocket-1{
  animation-timing-function: steps(5,jump-end);
}
.rocket-2{
  animation-timing-function: steps(5,jump-start);
}
.rocket-3{
  animation-timing-function: steps(5,jump-both);
}
.rocket-4{
  animation-timing-function: steps(5,jump-none);
}
@keyframes flight{
  0%{transform: none;}
  100%{transform: translateX(500px);}
}

Результат:

Глобальные временные функции

Работают для всех свойств CSS, в том числе:

  • inherit: даёт дочернему элементу наследуемые свойства родительского. Если свойства не наследуются, то возвращаются к initial.

  • initial: кажется, что initial — способ использовать дефолтные значения свойств, но это не всегда так. У временных функций initial совпадает с дефолтным значением ease.

  • revert: устанавливает свойства элемента как дефолтные CSS свойства браузера.

  • unset: похоже на  revert, но с дополнениями. Влияет на наследуемые и ненаследуемые свойства.

Свойства выше — наследуемые. К сожалению, animation-timing-function и transition-timing-function не наследуются.

Итак, если свойство наследуется, unset присваивает ему значение inherit. Если нет — свойству присваивается значение initial.

Бонус: animation-delay и transition-delay

Свойство -delay можно использовать с временными функциями: animation-delay и transition-delay откладывают старт анимации. Добавить свойство можно при помощи сокращения:

animation: <name> <duration> <timing-function> <delay>;

transition: <property> <duration> <timing-function> <delay>;

Порядок свойств неважен, вторым значением времени в объявлении всегда будет свойство delay. Если у нас такое объявление:

transition: transform 2s 1s ease;

Тогда transition-delay равняется 1s. Это же относится animation.

Покажем на примере:

.rocket-1{
   transition-timing-function: cubic-bezier(.66,.39,.21,.67);
   transition-delay: 500ms;
}
.rocket-2{
   transition-timing-function: cubic-bezier(.66,.39,.21,.67);
   transition-delay: 700ms;
}
.rocket-3{
  transition-timing-function: cubic-bezier(.66,.39,.21,.67);
  transition-delay: 1s;
}

Такое свойство можно использовать для загрузки разных разделов веб-страницы без необходимости определять разные  animation или transition для каждого раздела.

Курсы по программированию для получения новой специальности или повышения:

Топ бесплатных курсов и занятий: