Создание JavaScript-анимации

Двумерная и трехмерная анимация, создаваемая как традиционными, так и компьютерными методами, основана на одном и том же принципе: если ряд статичных изображений показать в достаточно быстром темпе, то человеческий глаз свяжет их вместе и примет за непрерывное движение.

Наиболее очевидный и распространенный вид анимации в компьютерной графике – это спрайтовая анимация.

Спрайт — графический объект в компьютерной графике, чаще всего растровое изображение, свободно перемещающееся по экрану.

Наиболее простой вид спрайта – это прямоугольное изображение. Для начального его определения необходимо задать размер и положение.

Перемещение спрайта по экрану определяет изменение координат его положения с течением времени.

Т.е. необходима функция, которая срабатывает с определенным интервалом времени. И такая функция в JS есть, и не одна.

  • setTimeout позволяет вызвать функцию один раз через определённый интервал времени.
  • setInterval позволяет вызывать функцию регулярно, повторяя вызов через определённый интервал времени.

setTimeout
Синтаксис:

var timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)
Параметры:

func|code - Функция или строка кода для выполнения. Обычно это функция. По историческим причинам можно передать и строку кода, но это не рекомендуется.

delay - Задержка перед запуском в миллисекундах (1000 мс = 1 с). Значение по умолчанию – 0.

arg1, arg2… - Аргументы, передаваемые в функцию.

Отмена через clearTimeout
Вызов setTimeout возвращает «идентификатор таймера» timerId, который можно использовать для отмены дальнейшего выполнения.

Синтаксис для отмены:

var timerId = setTimeout(...);
clearTimeout(timerId);

setInterval
Метод setInterval имеет такой же синтаксис как setTimeout:

var timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
Все аргументы имеют такое же значение. Но отличие этого метода от setTimeout в том, что функция запускается не один раз, а периодически через указанный интервал времени.

Чтобы остановить дальнейшее выполнение функции, необходимо вызвать clearInterval(timerId).

Пример анимации линейного спрайтового движения:

Файл index.html:

<html>
<head>
    <meta charset="utf-8" />
 <script src="test.js"></script>
</head>
  <body onload="init()">
    <canvas id="tutorial" width="500" height="500"></canvas><br />
    <input type="button" onclick="start()" value="Пуск">
    <input type="button" onclick="stop()" value="Стоп">
  </body>
</html>

Файл test.js:

var x;
var Idint;
var ctx;
 
function init(){
  x=10;
  ctx = document.getElementById('tutorial').getContext('2d');
}
 
function draw() {
  ctx.fillStyle = 'white';
  ctx.fillRect(x,10,50,50);
  x=x+3;
  ctx.fillStyle = 'blue';
  ctx.fillRect(x,10,50,50);
}
 
function start() {
  Idint = setInterval(draw, 100);
}
 
function stop() {
  clearInterval(Idint);
}

В действительности лучше отображать меньшее количество кадров в секунду, но сделать это количество постоянным. Дело в том, что наш глаз воспринимает небольшие отклонения в частоте, и несколько выпавших кадров режут глаз больше, чем более низкое количество кадров в секунду. Вот здесь на помощь приходит встроенный в HTML5 API requestAnimationFrame.

Преимущества requestAnimationFrame
requestAnimationFrame дает браузеру возможность контролировать, сколько кадров он может обработать. Вместо того, чтобы требовать от браузера отображать кадры, которые в итоге выпадут, вы разрешаете ему показывать кадры тогда, когда они обработаны, и с постоянной частотой. Польза от этого двояка:

  • Анимация выглядит более плавной, поскольку уровень кадров в секунду остается постоянной.
  • Процессор не перегружается задачами по рендерингу, а может обрабатывать и другие задачи во время рендеринга анимации. Вообще браузер может определить тот уровень кадров в секунду, который будет оптимален для задач, которые браузер выполняет одновременно с анимацией.

Синтаксис:

var requestId = requestAnimationFrame(callback)
Такой вызов планирует запуск функции callback на ближайшее время, когда браузер сочтёт возможным осуществить анимацию.

Пример анимации линейного спрайтового движения:

Файл test.js:

var x;
var Idint;
var ctx;
 
function init(){
  x=10;
  ctx = document.getElementById('tutorial').getContext('2d');
}
 
function draw() {
  if (x<450) {
  ctx.fillStyle = 'white';
  ctx.fillRect(x,10,50,50);
  x=x+3;
  ctx.fillStyle = 'blue';
  ctx.fillRect(x,10,50,50);
  Idint = requestAnimationFrame(draw);
  } else {cancelAnimationFrame(Idint);}
}
 
function start() {
  Idint = requestAnimationFrame(draw);
}
 
function stop() {
  cancelAnimationFrame(Idint);
}

Оптимальная реализация анимации объектов

Хорошим стилем создания каких-либо анимаций является разделение математических расчетов и прорисовки. Для демонстрации этого принципа создадим четыре функции: play, render, update и requestAnimFrame.

Функция update будет отвечать за математические расчеты: просчитывать координаты анимируемых объектов, проверять их на столкновения и пр.

Функция render будет отображать все необходимые объекты на холсте, т.е. заниматься прорисовкой.

Функция play поочередно вызовет render, update и requestAnimFrame.

Функция requestAnimFrame указывает браузеру на то, что необходимо произвести анимацию, и просит его запланировать перерисовку на следующем кадре анимации. В качестве параметра метод получает функцию, которая будет вызвана перед перерисовкой.

Структура кода с универсальной функцией requestAnimFrame приведена ниже:

function play(){
  update(); // Расчеты
  render(); // Прорисовка
  requestAnimFrame(play); //Функция play должна повторяться, чтоб работала анимация
}
 
function update(){
}
 
function render(){
}
 
var requestAnimFrame = (function() {
	return window.requestAnimationFrame ||
	window.webkitRequestAnimationFrame ||
	window.mozRequestAnimationFrame ||
	window.oRequestAnimationFrame ||
	window.msRequestAnimationFrame ||
	function(callback) {
		window.setTimeout(callback, 50);
	};
})();
 
play(); // Запуск анимации 

Стоит обратить внимание, как реализована функция requestAnimFrame: для различных браузеров предлагается соответствующий вызов по имени функции, встроенной в их движок. В случае, если браузер не поддерживает ее, то вызывается универсальная функция setTimeOut.