В этой главе будут показаны различные способы добавить на веб-страничку анимацию.
Внешний вид странички определяется CSS-стилями. Стили элементов можно менять в любой момент, обращаясь внутри скрипта к свойству style
. Вот пример:
var foo = document.getElementById("foo");
foo.style.color = 'black';
foo.style['background-color'] = 'red';
foo.style.width = '200px';
В этом примере стоит обратить внимание на два важных момента: если CSS-свойство содержит в своём названии дефис, то его следует либо указывать внутри квадратных скобок текстовым литералом, либо «верблюжьим регистром» (вместо background-color
нужно писать backgroundColor
).
Второй момент состоит в том, что в свойствах, требующих единицы измерения, нельзя эти единицы опускать или ставить перед ними пробел. Например, если высота элемента хранится в переменной bar
и является числом, то её нужно выставлять так:
foo.style.height = bar + 'px';
В заключение раздела напомним про позиционирование элементов при помощи явно заданных координат. Есть два вида явно позиционируемых элементов: со свойством position:relative
и со свойстом position:absolute
. Координаты задаются при помощи свойств left
, right
, top
, bottom
(конечно, не всех четырёх сразу, а не более чем двух ортогональных друг другу).
Элементы первого вида располагаются на странице автоматически, а координаты задают смещение изображения элемента относительно его положения. Положение изображения элемента никак не влияет на взаимное расположение элементов на странице.
Элементы второго вида вообще не располагаются на странице, а только рисуются по указанным координатам. При этом важный момент состоит в том, что координаты задают смещение изображения относительно ближайшего родителя, у которого свойство position
отличается от значения по-умолчанию (position:static
). Отсюда следствие: если хочется расположить элемент относительно какого-то его автоматически позиционированного родителя, этому родителю следует задать свойство position:relative
без каких-либо смещений.
Иногда при позиционировании элементов возникает вопрос о том, какой элемент рисуется поверх какого. Решается этот вопрос при помощи свойства z-index
: чем больше числовое его значение, тем «выше» расположен элемент (формально это означает, что для двух элементов с разным значением z-index
поверх рисуется тот, у которого значение больше).
Важной особенностью Javascript (и основным отличием от большинства языков программирования) является реактивная модель исполнения.
Традиционная последовательная модель исполнения предполагает, что программа выполняется последовательно, начиная от точки входа. Даже при наличии нескольких исполнителей, работающих одновременно, каждый из них выполняет свою часть программы последовательно.
Совсем по-другому работает реактивная модель. У программы вообще нет точки входа. Её подпрограммы (в случае Javascript – функции) запускаются в ответ на какие-то внешние события. Особенностью реактивной модели Javascript является наличие в каждый момент времени не более одного исполнителя. Поэтому, например, код
while (true) {}
приводит к невозможности дальнейшей обработки каких бы то ни было событий.
В связи с этим функции, требующие больших затрат вычислительных ресурсов, приходится разбивать на мелкие части, вызывающие друг друга отложенно.
Отложенный вызов осуществляется функцией setTimeout
. У неё есть два варианта:
setTimeout(foo,bar) // вызов функции foo через (приблизительно) bar милисекунд
setTimeout(foo) // вызов функции foo при первой возможности
Оба варианта приводят к тому, что в некоторый момент генерируется событие, при обработке которого происходит исполнение указанной функции. Например, ресурсозатратную функцию вида
function freezeBrowser() {
sum = 0;
for (var i = 0; i < LARGE_NUMBER; i++) {
sum += doLongCalculation();
}
return sum;
}
можно переписать таким образом
function calculateSum(whatToDoWithSum) {
doStep(0, 0, whatToDoWithSum);
}
function doStep(step, sum, continuation) {
if (step >= LARGE_NUMBER) { continuation(sum); return; }
sum += doLongCalculation();
setTimeout(function() {doStep(step+1, sum, continuation)});
}
Обратите внимание, что поскольку вычисление суммы завершится в какой-то момент в будущем, напрямую получить эту сумму можно только при помощи глобальной переменной. Вообще говоря, изменяемые глобальные переменные – источник большого количества трудноуловимых ошибок. Поэтому традиционным является т.н. Continuation Passing Style – передача функции, осуществляющей вычисление, действия, которое должно быть произведено по окончании вычисления.
К сожалению, недостатком наивной формы такого подхода является очень трудночитаемый код. К счастью, CPS можно структурировать при помощи, как ни странно, монад (пришедших в программирование напрямую из абстрактной алгебры). Сейчас мы не будем трогать это понятие, но в одной из следующих глав мы обсудим конкретный пример монады в Javascript (тип Promise
).
Наиболее просто устроенный и одновременно наиболее мощный способ анимировать что-либо: делать это при помощи setTimeout
или requestAnimationFrame
. Как работает setTimeout
, мы пояснили в предыдущем разделе.
Функция requestAnimationFrame
предоставляет немного больше автоматики: во-первых, она откладывает вызовы так, чтобы функция, отложенно вызывающая себя, делала это с некоторой более-менее фиксированной частотой (обычно около 60 раз в секунду). Во-вторых, она передаёт в отложенную функцию момент её вызова.
Приведём пример анимации движения элемента по окружности.
function initialize() {
var foo = document.createElement('div');
document.body.appendChild(foo);
foo.style.width = "40px";
foo.style.height = "40px";
foo.style.backgroundColor = "red";
foo.style.position = "absolute";
foo.style.left = '100px';
foo.style.top = '100px';
requestAnimationFrame(function(t){animate(foo,t,t)});
}
function animate(foo,startTime,currentTime) {
var t = currentTime - startTime;
// разность моментов времени -- в милисекундах
var offsetX = 40*Math.cos(t/1000);
var offsetY = 40*Math.sin(t/1000);
foo.style.left = (60 + offsetX) + 'px';
foo.style.top = (60 + offsetY) + 'px';
requestAnimationFrame(function(t){animate(foo,startTime,t)});
}
В CSS есть специальное свойство transition
. Оно позволяет сопоставить некоторым свойствам временной промежуток. При изменении указанных свойств они будут изменяться не мгновенно, а в течение указанного промежутка. Например:
#foo {
transition: left 2s, top 2s, background-color 1s;
left: 0px;
top: 0px;
background-color: black;
width: 60px;
height: 60px;
position: absolute;
}
#foo:hover {
left: 100px;
top: 200px;
background-color: red;
}
Также в CSS определено свойство animation
с кучей подсвойств. Наиболее важны следующие три:
#foo {
animation-name: foobar; /* имя раскадровки */
animation-iteration-count: infinite; /* количество итераций */
animation-duration: 5s; /* время одной итерации */
}
Раскадровка задаётся немного неудобно:
@keyframes foobar {
0% { left: 20px; width: 20px; }
10% { left: 40px; }
20% { left: 50px; width: 60px; }
30% { left: 60px; }
66.6% { left: 20px; }
90% { left: 90px; width: 20px; }
100% { left: 20px; }
}
Положение кадра во времени задаётся в процентах от всей длительности анимации. Сам кадр – произвольный набор свойств, поддающихся анимации. Важный момент: каждое свойство анимируется независимо от остальных. Например, в вышеприведённой раскадровке ширина изменяется от 60 до 20 между 20% и 90%, а не между 20% и 30%.
В заключение скажем, что выключить анимацию можно, установив из скрипта animation-duration: 0s
или же animation-name: none
.
@ 2016 arbrk1, all rights reversed