CSS позволяет создавать простые анимации без использования JavaScript.
JavaScript может быть использован для управления такими 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 записывается список свойств, изменения которых необходимо анимировать, например: left, margin-left, height, color.
Анимировать можно не все свойства, но многие из них. Значение свойства all означает "анимируй все свойства".
В transition-duration можно определить, сколько времени займёт анимация. Время должно быть задано в формате времени CSS: в секундах s или миллисекундах ms.
В transition-delay можно определить задержку перед началом анимации. Например, если transition-delay: 1s, тогда анимация начнётся через 1 секунду после изменения свойства.
Отрицательные значения также допустимы. В таком случае анимация начнётся с середины. Например, если transition-duration равно 2s, а transition-delay -- -1s, тогда анимация займёт одну секунду и начнётся с середины.
Здесь приведён пример анимации, сдвигающей цифры от 0 до 9 с использованием CSS-свойства transform со значением translate:
[codetabs src="digits"]
Свойство transform анимируется следующим образом:
#stripe.animate {
transform: translate(-90%);
transition-property: transform;
transition-duration: 9s;
}В примере выше JavaScript-код добавляет класс .animate к элементу, после чего начинается анимация:
stripe.classList.add('animate');Можно начать анимацию "с середины", с определённого числа, например, используя отрицательное значение transition-delay, соответствующее необходимому числу.
Если вы нажмёте на цифру ниже, то анимация начнётся с текущей секунды:
[codetabs src="digits-negative-delay"]
JavaScript делает это с помощью нескольких строк кода:
stripe.onclick = function() {
let sec = new Date().getSeconds() % 10;
*!*
// например, значение -3s здесь начнёт анимацию с третьей секунды
stripe.style.transitionDelay = '-' + sec + 's';
*/!*
stripe.classList.add('animate');
};Временная функция описывает, как процесс анимации будет распределён во времени. Будет ли она начата медленно и затем ускорится или наоборот.
На первый взгляд это очень сложное свойство, но оно становится понятным, если уделить ему немного времени.
Это свойство может принимать два вида значений: кривую Безье или количество шагов. Давайте начнём с кривой Безье, как с наиболее часто используемой.
Временная функция может быть задана, как кривая Безье с 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.
В примере ниже поезд "едет" слева направо с одинаковой скоростью (нажмите на поезд):
[codetabs src="train-linear"]
В свойстве 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).
Её график:
Как видим, анимация начинается быстро: кривая быстро поднимается вверх, и затем все медленнее и медленнее.
Ниже временная функция в действии (нажмите на поезд):
[codetabs src="train"]
CSS:
.train {
left: 0;
transition: left 5s cubic-bezier(0, 0.5, 0.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, 0.5, 0.5, 1); */
}Но получившийся результат немного отличается.
Кривая Безье может заставить анимацию "выпрыгивать" за пределы диапазона.
Контрольные точки могут иметь любые значения по оси y: отрицательные или сколь угодно большие. В таком случае кривая Безье будет скакать очень высоко или очень низко, заставляя анимацию выходить за её нормальные пределы.
В приведённом ниже примере код анимации:
.train {
left: 100px;
transition: left 5s cubic-bezier(0.5, -1, 0.5, 2);
/* JavaScript sets left to 400px */
}Свойство left будет анимироваться от 100px до 400px.
Но когда вы нажмёте на поезд, вы увидите следующее:
- Сначала, поезд поедет назад:
leftстанет меньше, чем100px. - Затем он поедет вперёд, немного дальше, чем
400px. - И затем вернётся назад в значение
400px.
[codetabs src="train-over"]
Если мы взглянем на кривую Безье из примера, становится понятно поведение поезда.
Мы вынесли координату y для первой опорной точки ниже нуля и выше единицы для третьей опорной точки, поэтому кривая вышла за пределы "обычного" квадрата. Значения y вышли из "стандартного" диапазона 0..1.
Как мы знаем, ось y измеряет "завершённость процесса анимации". Значение y = 0 соответствует начальному значению анимируемого свойства и y = 1 -- конечному значению. Таким образом, y<0 делает значение свойства left меньше начального значения и y>1 -- больше конечного.
Это, конечно, "мягкий" вариант. Если значение y будут -99 и 99, то поезд будет гораздо сильнее "выпрыгивать" за пределы.
Как сделать кривую Безье необходимую для конкретной задачи? Существует множество инструментов.
- К примеру, мы можем сделать это на сайте https://cubic-bezier.com.
- Браузернные инструменты разработчика также имеют специальную поддержку для создания кривых Безье в CSS:
- Откройте инструменты разработчика при помощи
key:F12(Mac:key:Cmd+Opt+I). - Выберете вкладку
Elements, затем обратите внимание на под-панельStylesв правой стороне. - Свойства CSS со словом
cubic-bezierбудут иметь иконку перед этим словом. - Кликните по иконке, чтобы отредактировать кривую.
- Откройте инструменты разработчика при помощи
Временная функция steps(количество шагов[, start/end]) позволяет разделить анимацию на шаги.
Давайте рассмотрим это на уже знакомом нам примере с цифрами.
Ниже представлен список цифр, без какой-либо анимации, который мы будем использовать в качестве основы:
[codetabs src="step-list"]
В 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 -- означает, что в начале анимации нам необходимо перейти на первый шаг немедленно.
В действии:
[codetabs src="step"]
Щелчок по цифре немедленно изменяет её на 1 (первый шаг), а затем изменяется в начале следующей секунды.
Анимация будет происходить так:
0s---10%(первое изменение в начале первой секунды, сразу после нажатия)1s---20%- ...
8s---90%- (на протяжении последней секунды отображается последнее значение).
Здесь первое изменение было немедленным из-за start в steps.
Альтернативное значение end означало бы, что изменения нужно применять не в начале, а в конце каждой секунды.
Анимация будет происходить так:
0s--01s---10%(первое изменение произойдёт в конце первой секунды)2s---20%- ...
9s---90%
Пример step(9, end) в действии (обратите внимание на паузу перед первым изменением цифры):
[codetabs src="step-end"]
Существуют также некоторые заранее определённые сокращения для steps(...):
step-start-- то же самое, чтоsteps(1, start). Оно означает, что анимация начнётся сразу и произойдёт в один шаг. Таким образом она начнётся и завершится сразу, как будто и нет никакой анимации.step-end-- то же самое, чтоsteps(1, end): выполнит анимацию за один шаг в концеtransition-duration.
Такие значения используются редко, потому что это не совсем анимация, а точнее будет сказать одношаговые изменения. Мы упоминаем их здесь для полноты картины.
Когда завершается анимация, срабатывает событие transitionend.
Оно широко используется для выполнения действий после завершения анимации, а также для создания последовательности анимаций.
Например, корабль в приведённом ниже примере начинает плавать туда и обратно по клику, каждый раз всё дальше и дальше вправо:
[iframe src="boat" height=300 edit link]
Анимация начинается с помощью функции 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-анимации, которые позволяют решать более сложные задачи.