CSS позволяет создавать простые анимации без использования JavaScript.
JavaScript может быть использован для управления такими CSS-анимациями. Это позволяет делать более сложные анимации, используя небольшие кусочки кода.
CSS-переходы
Идея CSS-переходов проста: мы указываем, что некоторое свойство должно быть анимировано, и как оно должно быть анимировано. А когда свойство меняется, браузер сам обработает это изменение и отрисует анимацию.
Всё что нам нужно, чтобы начать анимацию – это изменить свойство, а дальше браузер сделает плавный переход сам.
Например, CSS-код ниже анимирует трёх-секундное изменениеbackground-color:
.animated {
transition-property: background-color;
transition-duration: 3s;
}
Теперь, если элементу присвоен класс .animated, любое изменение свойства background-color будет анимироваться в течение трёх секунд.
Нажмите кнопку ниже, чтобы анимировать фон:
<button id="color">Нажми меня</button>
<style>
#color {
transition-property: background-color;
transition-duration: 3s;
}
</style>
<script>
color.onclick = function() {
this.style.backgroundColor = 'red';
};
</script>
Существует 4 свойства для описания CSS-переходов:
transition-property– свойство переходаtransition-duration– продолжительность переходаtransition-timing-function– временная функция переходаtransition-delay– задержка начала перехода
Далее мы рассмотрим их все, а сейчас ещё заметим, что есть также общее свойство transition, которое позволяет задать их одновременно в последовательности: property duration timing-function delay, а также анимировать несколько свойств одновременно.
Например, у этой кнопки анимируются два свойства color и font-size одновременно:
<button id="growing">Нажми меня</button>
<style>
#growing {
transition: font-size 3s, color 2s;
}
</style>
<script>
growing.onclick = function() {
this.style.fontSize = '36px';
this.style.color = 'red';
};
</script>
Теперь рассмотрим каждое свойство анимации по отдельности.
transition-property
В transition-property записывается список свойств, изменения которых необходимо анимировать, например: left, margin-left, height, color.
Анимировать можно не все свойства, но многие из них. Значение свойства all означает «анимируй все свойства».
transition-duration
В transition-duration можно определить, сколько времени займёт анимация. Время должно быть задано в формате времени CSS: в секундах s или миллисекундах ms.
transition-delay
В transition-delay можно определить задержку перед началом анимации. Например, если transition-delay: 1s, тогда анимация начнётся через 1 секунду после изменения свойства.
Отрицательные значения также допустимы. В таком случае анимация начнётся с середины. Например, если transition-duration равно 2s, а transition-delay – -1s, тогда анимация займёт одну секунду и начнётся с середины.
Здесь приведён пример анимации, сдвигающей цифры от 0 до 9 с использованием CSS-свойства transform со значением translate:
stripe.onclick = function() {
stripe.classList.add('animate');
};#digit {
width: .5em;
overflow: hidden;
font: 32px monospace;
cursor: pointer;
}
#stripe {
display: inline-block
}
#stripe.animate {
transform: translate(-90%);
transition-property: transform;
transition-duration: 9s;
transition-timing-function: linear;
}<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
Кликните на цифру для начала анимации:
<div id="digit"><div id="stripe">0123456789</div></div>
<script src="script.js"></script>
</body>
</html>Свойство transform анимируется следующим образом:
#stripe.animate {
transform: translate(-90%);
transition-property: transform;
transition-duration: 9s;
}
В примере выше JavaScript-код добавляет класс .animate к элементу, после чего начинается анимация:
stripe.classList.add('animate');
Можно начать анимацию «с середины», с определённого числа, например, используя отрицательное значение transition-delay, соответствующие необходимому числу.
Если вы нажмёте на цифру ниже, то анимация начнётся с последней секунды:
stripe.onclick = function() {
let sec = new Date().getSeconds() % 10;
stripe.style.transitionDelay = '-' + sec + 's';
stripe.classList.add('animate');
};#digit {
width: .5em;
overflow: hidden;
font: 32px monospace;
cursor: pointer;
}
#stripe {
display: inline-block
}
#stripe.animate {
transform: translate(-90%);
transition-property: transform;
transition-duration: 9s;
transition-timing-function: linear;
}<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
Click below to animate:
<div id="digit"><div id="stripe">0123456789</div></div>
<script src="script.js"></script>
</body>
</html>JavaScript делает это с помощью нескольких строк кода:
stripe.onclick = function() {
let sec = new Date().getSeconds() % 10;
// например, значение -3s здесь начнут анимацию с третьей секунды
stripe.style.transitionDelay = '-' + sec + 's';
stripe.classList.add('animate');
};
transition-timing-function
Временная функция описывает, как процесс анимации будет распределён во времени. Будет ли она начата медленно и затем ускорится или наоборот.
На первый взгляд это очень сложное свойство, но оно становится понятным, если уделить ему немного времени.
Это свойство может принимать два вида значений: кривую Безье или количество шагов. Давайте начнём с кривой Безье, как с наиболее часто используемой.
Кривая Безье
Временная функция может быть задана, как кривая Безье с 4 контрольными точками, удовлетворяющими условиям:
- Первая контрольная точка:
(0,0). - Последняя контрольная точка:
(1,1). - Для промежуточных точек значение
xдолжно быть0..1, значениеyможет принимать любое значение.
Синтаксис для кривых Безье в CSS: cubic-bezier(x2, y2, x3, y3). Нам необходимо задать только вторую и третью контрольные точки, потому что первая зафиксирована со значением (0,0) и четвёртая – (1,1).
Временная функция описывает то, насколько быстро происходит анимации во времени.
- Ось
x– это время:0– начальный момент,1– последний моментtransition-duration. - Ось
yуказывает на завершение процесса:0– начальное значение свойства,1– конечное значение.
Самым простым примером анимации является равномерная анимация с линейной скоростью. Она может быть задана с помощью кривой cubic-bezier(0, 0, 1, 1).
Вот как выглядит эта «кривая»:
…Как мы видим, это прямая линия. Значению времени (x) соответствует значение завершённости анимации (y), которое равномерно изменяется от 0 к 1.
В примере ниже поезд «едет» слева направо с одинаковой скоростью (нажмите на поезд):
.train {
position: relative;
cursor: pointer;
width: 177px;
height: 160px;
left: 0;
transition: left 5s cubic-bezier(0, 0, 1, 1);
}<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='450px'">
</body>
</html>В свойстве transition указана следующая кривая Безье:
.train {
left: 0;
transition: left 5s cubic-bezier(0, 0, 1, 1);
/* JavaScript устанавливает свойство left равным 450px */
}
…А как показать замедляющийся поезд?
Мы можем использовать другую кривую Безье: cubic-bezier(0.0, 0.5, 0.5 ,1.0).
Её график:
Как видим, анимация начинается быстро: кривая быстро поднимается вверх, и затем все медленнее и медленнее.
Ниже временная функция в действии (нажмите на поезд):
.train {
position: relative;
cursor: pointer;
width: 177px;
height: 160px;
left: 0px;
transition: left 5s cubic-bezier(0.0, 0.5, 0.5, 1.0);
}<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='450px'">
</body>
</html>CSS:
.train {
left: 0;
transition: left 5s cubic-bezier(0, .5, .5, 1);
/* JavaScript устанавливает свойство left равным 450px */
}
Есть несколько встроенных обозначений кривых Безье: linear, ease, ease-in, ease-out и ease-in-out.
linear это короткая запись для cubic-bezier(0, 0, 1, 1) – прямой линии, которую мы видели раньше.
Другие названия – это также сокращения для других cubic-bezier:
ease* |
ease-in |
ease-out |
ease-in-out |
|---|---|---|---|
(0.25, 0.1, 0.25, 1.0) |
(0.42, 0, 1.0, 1.0) |
(0, 0, 0.58, 1.0) |
(0.42, 0, 0.58, 1.0) |
* – используется по умолчанию, если не задана другая временная функция.
Для того, чтобы замедлить поезд, мы можем использовать ease-out:
.train {
left: 0;
transition: left 5s ease-out;
/* transition: left 5s cubic-bezier(0, .5, .5, 1); */
}
Но получившийся результат немного отличается.
Кривая Безье может заставить анимацию «выпрыгивать» за пределы диапазона.
Контрольные точки могут иметь любые значения по оси y: отрицательные или сколь угодно большие. В таком случае кривая Безье будет скакать очень высоко или очень низко, заставляя анимацию выходить за её нормальные пределы.
В приведённом ниже примере код анимации:
.train {
left: 100px;
transition: left 5s cubic-bezier(.5, -1, .5, 2);
/* JavaScript sets left to 400px */
}
Свойство left будет анимироваться от 100px до 400px.
Но когда вы нажмёте на поезд, вы увидите следующее:
- Сначала, поезд поедет назад:
leftстанет меньше, чем100px. - Затем он поедет вперёд, немного дальше, чем
400px. - И затем вернётся назад в значение
400px.
.train {
position: relative;
cursor: pointer;
width: 177px;
height: 160px;
left: 100px;
transition: left 5s cubic-bezier(.5, -1, .5, 2);
}<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='400px'">
</body>
</html>Если мы взглянем на кривую Безье из примера, становится понятно поведение поезда.
Мы вынесли координату y для первой опорной точки ниже нуля и выше единицы для третьей опорной точки, поэтому кривая вышла за пределы «обычного» квадрата. Значения y вышли из «стандартного» диапазона 0..1.
Как мы знаем, ось y измеряет «завершённость процесса анимации». Значение y = 0 соответствует начальному значению анимируемого свойства и y = 1 – конечному значению. Таким образом, y<0 делает значение свойства left меньше начального значения и y>1 – больше конечного.
Это, конечно, «мягкий» вариант. Если значение y будут -99 и 99, то поезд будет гораздо сильнее «выпрыгивать» за пределы.
Как сделать кривую Безье необходимую для конкретной задачи? Существует множество инструментов.
- К примеру, мы можем сделать это на сайте https://cubic-bezier.com.
- Браузернные инструменты разработчика также имеют специальную поддержку для создания кривых Безье в CSS:
- Откройте инструменты разработчика при помощи F12 (Mac: Cmd+Opt+I).
- Выберете вкладку
Elements, затем обратите внимание на под-панельStylesв правой стороне. - Свойства CSS со словом
cubic-bezierбудут иметь иконку перед этим словом. - Кликните по иконке, чтобы отредактировать кривую.
Шаги
Временная функция steps(количество шагов[, start/end]) позволяет разделить анимацию на шаги.
Давайте рассмотрим это на уже знакомом нам примере с цифрами.
Ниже представлен список цифр, без какой-либо анимации, который мы будем использовать в качестве основы:
#digit {
border: 1px solid red;
width: 1.2em;
}
#stripe {
display: inline-block;
font: 32px monospace;
}<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="digit"><div id="stripe">0123456789</div></div>
</body>
</html>В HTML, вереница цифр заключена в <div id="digits"> фиксированной длины:
<div id="digit">
<div id="stripe">0123456789</div>
</div>
Div-элемент #digit имеет фиксированную ширину и границу, поэтому он выглядит как красное окно.
Мы сделаем таймер: цифры будут появляться одна за другой, дискретно.
Чтобы добиться этого, мы скроем #stripe за пределами #digit, используя overflow: hidden, а затем, шаг за шагом будем сдвигать #stripe влево.
Всего будет 9 шагов, один шаг для каждой цифры:
#stripe.animate {
transform: translate(-90%);
transition: transform 9s steps(9, start);
}
Первый аргумент временной функции steps(9, start) – количество шагов. Трансформация будет разделена на 9 частей (10% каждая). Временной интервал также будет разделён на 9 частей, таким образом свойство transition: 9s обеспечивает нам 9 секунд анимации, что даёт по одной секунде на цифру.
Вторым аргументом является одно из ключевых слов: start или end.
start – означает, что в начале анимации нам необходимо перейти на первый шаг немедленно.
В действии:
#digit {
width: .5em;
overflow: hidden;
font: 32px monospace;
cursor: pointer;
}
#stripe {
display: inline-block
}
#stripe.animate {
transform: translate(-90%);
transition-property: transform;
transition-duration: 9s;
transition-timing-function: steps(9, start);
}<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
Кликните на цифру для начала анимации:
<div id="digit"><div id="stripe">0123456789</div></div>
<script>
digit.onclick = function() {
stripe.classList.add('animate');
}
</script>
</body>
</html>Щелчок по цифре немедленно изменяет её на 1 (первый шаг), а затем изменяется в начале следующей секунды.
Анимация будет происходить так:
0s–-10%(первое изменение в начале первой секунды, сразу после нажатия)1s–-20%- …
8s–-90%- (на протяжении последней секунды отображается последнее значение).
Здесь первое изменение было немедленным из-за start в steps.
Альтернативное значение end означало бы, что изменения нужно применять не в начале, а в конце каждой секунды.
Анимация будет происходить так:
0s–01s–-10%(первое изменение произойдёт в конце первой секунды)2s–-20%- …
9s–-90%
Пример step(9, end) в действии (обратите внимание на паузу перед первым изменением цифры):
#digit {
width: .5em;
overflow: hidden;
font: 32px monospace;
cursor: pointer;
}
#stripe {
display: inline-block
}
#stripe.animate {
transform: translate(-90%);
transition-property: transform;
transition-duration: 9s;
transition-timing-function: steps(9, end);
}<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
Кликните на цифру для начала анимации:
<div id="digit"><div id="stripe">0123456789</div></div>
<script>
digit.onclick = function() {
stripe.classList.add('animate');
}
</script>
</body>
</html>Существуют также некоторые заранее определённые сокращения для steps(...):
step-start– то же самое, чтоsteps(1, start). Оно означает, что анимация начнётся сразу и произойдёт в один шаг. Таким образом она начнётся и завершится сразу, как будто и нет никакой анимации.step-end– то же самое, чтоsteps(1, end): выполнит анимацию за один шаг в концеtransition-duration.
Такие значения используются редко, потому что это не совсем анимация, а точнее будет сказать одношаговые изменения. Мы упоминаем их здесь для полноты картины.
Событие: «transitionend»
Когда завершается анимация, срабатывает событие transitionend.
Оно широко используется для выполнения действий после завершения анимации, а также для создания последовательности анимаций.
Например, корабль в приведённом ниже примере начинает плавать туда и обратно по клику, каждый раз все дальше и дальше вправо:
Анимация начинается с помощью функции go, которая вызывается каждый раз снова, когда переход заканчивается и меняется направление:
boat.onclick = function() {
//...
let times = 1;
function go() {
if (times % 2) {
// плыть вправо
boat.classList.remove('back');
boat.style.marginLeft = 100 * times + 200 + 'px';
} else {
// плыть влево
boat.classList.add('back');
boat.style.marginLeft = 100 * times - 200 + 'px';
}
}
go();
boat.addEventListener('transitionend', function() {
times++;
go();
});
};
Объект события transitionend содержит ряд полезных свойств:
event.propertyName- Имя свойства, анимация которого завершилась. Может быть полезным, если мы анимируем несколько свойств.
event.elapsedTime- Время (в секундах), которое заняла анимация, без учёта
transition-delay.
Ключевые кадры
Мы можем объединить несколько простых анимаций вместе, используя CSS-правило @keyframes.
Оно определяет «имя» анимации и правила: что, когда и где анимировать. После этого можно использовать свойство animation, чтобы назначить анимацию на элемент и определить её дополнительные параметры.
Ниже приведён пример с пояснениями:
<div class="progress"></div>
<style>
@keyframes go-left-right { /* объявляем имя анимации: "go-left-right" */
from { left: 0px; } /* от: left: 0px */
to { left: calc(100% - 50px); } /* до: left: 100%-50px */
}
.progress {
animation: go-left-right 3s infinite alternate;
/* применить анимацию "go-left-right" на элементе
продолжительностью 3 секунды
количество раз: бесконечно (infinite)
менять направление анимации каждый раз (alternate)
*/
position: relative;
border: 2px solid green;
width: 50px;
height: 20px;
background: lime;
}
</style>
Существует множество статей про @keyframes, а также детальная спецификация.
Скорее всего, вам нечасто понадобится @keyframes, разве что на вашем сайте все постоянно в движении.
Итого
CSS-анимации позволяют плавно, или не очень, менять одно или несколько свойств.
Они хорошо решают большинство задач по анимации. Также мы можем реализовать анимации через JavaScript, более подробно об этом – в следующей главе.
Ограничения CSS-анимаций в сравнении с JavaScript-анимациями:
- Простые анимации делаются просто.
- Быстрые и не создают нагрузку на CPU.
- JavaScript-анимации более гибкие. В них может присутствовать любая анимационная логика, как например «взорвать» элемент.
- Можно изменять не только свойства. Мы можем создавать новые элементы с помощью JavaScript для анимации.
Большинство анимаций может быть реализовано с использованием CSS, как описано в этой главе. А событие transitionend позволяет запускать JavaScript после анимации, поэтому CSS-анимации прекрасно интегрируются с кодом.
Но в следующей главе мы рассмотрим некоторые JavaScript-анимации, которые позволяют решать более сложные задачи.
Комментарии
<code>, для нескольких строк кода — тег<pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)