Xiper

Программная анимация

Автор: Евгений Рыжков Дата публикации:
Последнее обновление:

Задача

Выполнить анимацию без использования фреймворков.

Решение

HTML:

<div id="test"></div>

CSS:

#test {
	position: relative;
	left: 0;
	width: 50px;
	height: 50px;
	background: #000;
}
window.onload = function(){
	var	block = window.document.getElementById("test"),	// элемент
		anim,									// таймаут
		start,									// время старта
		now,										// текущее время
		duration = 1000,							// продолжительность
		from = 0,									// стартовая позиция
		to = window.innerWidth/2,					// финишная позиция
		progress = 0,								// прогресс анимации
		x;										// позиция в текущий момент времени
	// закон приращения аргумента (easing)
	function delta(param){
		return param;
	};
	// рендер
	function render(){
		now = new Date().getTime();
		progress = (now-start)/duration;
		x = (to - from)*delta(progress) + from;
		test.style.left = x+"px";
		// если не конец выполняем анимацию еще
		if (progress < 1) anim = setTimeout(arguments.callee, 0)
			// иначе заканчиваем анимацию
			else
			{
				clearTimeout(anim);
				progress = 0;
			};
	};
	window.onclick = function(){
		start = new Date().getTime();
		render();
	};
};

Индикатор статуса анимации — progress, изменяется в интервале от 0 до 1. На базе этого значения высчитывается положение анимируемого объекта. Если анимация не закончена, функция render() вызывается снова и снова через таймаут.

Посмотреть в живую.

Оптимизация

Производители браузеров побеспокоились об оптимизации процесса анимации и предоставили нам API, которое помогает уменьшить число reflow и repaint. Имя ему RequestAnimationFrame.

Довольно показательный пример.

Немного изменяем JS код:

window.onload = function(){
	var	block = window.document.getElementById("test"),	// элемент
		anim,									// таймаут
		start,									// время старта
		now,										// текущее время
		duration = 1000,							// продолжительность
		from = 0,									// стартовая позиция
		to = window.innerWidth/2,					// финишная позиция
		progress = 0,								// прогресс анимации
		x;										// позиция в текущий момент времени
	// закон приращения аргумента (easing)
	function delta(param){
		return param;
	};
	// рендер
	function render(){
		now = new Date().getTime();
		progress = (now-start)/duration;
		x = (to - from)*delta(progress) + from;
		test.style.left = x+"px";
		// если не конец выполняем анимацию еще
		if (progress < 1) anim = setAnimation(render)
			// иначе заканчиваем анимацию
			else
			{
				clearAnimation(anim);
				progress = 0;
			};
	};
	window.onclick = function(){
		start = new Date().getTime();
		render();
	};
};	
//requestAnimFrame
window.setAnimation = (function() {
return	window.requestAnimationFrame||
		window.webkitRequestAnimationFrame||
		window.mozRequestAnimationFrame||
		window.oRequestAnimationFrame||
		window.msRequestAnimationFrame||
		function(/* function */callback, /* DOMElement */element) {
			return window.setTimeout(callback, 1000 / 60);
		};
})();
//canсelRequestAnimFrame
window.clearAnimation = (function() {
return	window.cancelRequestAnimationFrame||
		window.webkitCancelRequestAnimationFrame||
		window.mozCancelRequestAnimationFrame||
		window.oCancelRequestAnimationFrame||
		window.msCancelRequestAnimationFrame||
		function(id){clearTimeout(id)}
})();

Посмотреть в живую.

Т.к. API еще находится в стадии разработки, то приходится писать длинное условие "прощупывания" поддержки метода. Для браузеров, которые не поддерживают RequestAnimationFrame, в итоге используется setTimeout.

Материалы

  • Основы программной анимации на JavaScript
  • Переменная arguments
  • Продвинутые анимации с requestAnimationFrame