Анимация — несколько рисунков, показанных последовательно, чтобы создать иллюзию движения. Анимации привлекают внимание пользователей и помогают сделать интерфейсы понятными. Мы подготовили перевод статьи, чтобы разобраться, как контролировать движения и переходы в 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
:
linear
ease
ease-in
ease-ou
tease-in-out
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()
в дополнение к количеству шагов:
jump-start
jump-end
jump-both
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 для каждого раздела.