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
–0
1s
–-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-анимации, которые позволяют решать более сложные задачи.