<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Animation demo</title>
<style type="text/css">
#red {
width: 100px;
height: 100px;
margin: 20px;
background-color: #e24900;
font-size: 1px;
}
#green {
width: 100px;
height: 100px;
margin: 20px;
background-color: #3aca02;
font-size: 1px;
}
#text {
border: 1px solid black;
font-family: Verdana;
font-size: 14px;
color: black;
line-height: 15px;
height: 15px;
margin: 20px;
}
</style>
<script type="text/javascript">
<!--
/*============================- Animation queue -==========================*/
AnimationQueue.prototype.settings={
frameDelay: 24
};
/**
* Анимационный поток, представляет собой цепочку анимационных функций,
* запускаеммх с заданной частотой. Можно представить как процессор и тактовый
* генератор =)
*/
function AnimationQueue() {
this.anims=[];
this.currentAnim=0;
this.timer=null;
this.waittime=null;
this.fromTime=null;
this.loop=true;
}
/**
* Установить повторение анимации после завершения.
* По теории этого метода не должно быть. Выбираеться дефолтовое действие:
* повторять(у нас так) либо не повторять. Затем если нужно выполнить действие
* не равное дефолтовому, то добавляеться обьект "анимация" в ряд, что и выполнит
* необходимое действие.
* НО! на практике чем меньше телодвижений, тем лучше =)
* @param boolean loop true - поставить повтор, false - отменить повтор
*/
AnimationQueue.prototype.setLoop=function(loop) {
this.loop=Boolean(loop);
}
/**
* Установить паузу для следующего "кадра".
* Метод необходим анимациям, работающих гораздо медленней чем "анимационный
* поток". Время задаёться в миллисекундах, но меньше чем шаг аниматора быть не
* может. Чем меньше время, тем мнее точней будет результирующая пауза.
* @param integer ms пауза в миллисекундах
*/
AnimationQueue.prototype.wait=function(ms) {
if(typeof(ms)!='number') throw "AnimationQueue.wait: wait time '"+ms+"' must be an integer representing time in milliseconds";
this.waittime=ms;
this.fromTime=(new Date()).getTime();
}
/**
* Следующая анимация.
* Методы вызываеться анимациями, когда текущая полностью завершиться, что бы
* отдать управлениие следующей анимации.
*/
AnimationQueue.prototype.next=function() {
if(!this.loop && (++this.currentAnim)>=this.anims.length) this.stop();
else if(this.loop) this.currentAnim=(++this.currentAnim)>=this.anims.length? 0: this.currentAnim;
this.waittime=null;
}
/**
* Добавить анимацию в "анимационный поток".
* Метод добавляет анимации в конец цепочки. Можно добавлять новые анимации после
* вызова start, но это может порождать труднонаходимые глюки =)
* Поэтому набиваем поток анимациями заранее, затем вызываем start() и забываем.
* @param function animation функция аниматор, что будет вызываться каждый проход
* аниматора. Параметром получает ссылку на обьект аниматора.
*/
AnimationQueue.prototype.add=function(animation) {
if(!(animation instanceof Function)) throw "AnimationQueue.add: given animation object '"+animation+"' is not a function!";
this.anims[this.anims.length]=animation;
}
/**
* Запустить анимационный поток.
* Выполнение продолжаеться с момента остановки. Параметром можно указать с какой
* анимации начинать выполнение, отсчёт от 0
* @param integer порядковый номер стартовой анимации с 0
*/
AnimationQueue.prototype.start=function(initAnim) {
if(this.timer!=null||this.anims.length<1) return;
if(typeof(initAnim)=='number') this.currentAnim=initAnim;
this.currentAnim=this.currentAnim<0? 0: this.currentAnim>=this.anims.length? this.anims.length-1: this.currentAnim;
var animator=this;
this.timer=window.setInterval(function() {
if(animator.waittime!=null) {
if(((new Date()).getTime()-animator.fromTime)<animator.waittime) return;
animator.waittime=null;
}
animator.anims[animator.currentAnim](animator);
}, this.settings.frameDelay);
}
/**
* Остановить анимационный поток.
* Все анимации сохраняют своё состояние! Проще говоря останавливаеться главный
* таймер, функции-анимации просто перестают регулярно вызываться. При следующем
* вызове start() выполнение продолжиться со состояния на момент stop().
*/
AnimationQueue.prototype.stop=function() {
window.clearInterval(this.timer);
}
/*===============================- Animation Delay -=======================*/
/**
* Анимация - пауза.
* Этот обьект-обёртка понадобился для работы с паузами в пакетах
* анимаций(см. ниже). Представляеться как обычная анимационная функция.
* Передаёт управление по прошествии определённого интервала времени.
* Пауза срабатывает только один раз, для повторяющихся пауз нужно задать третьим
* параметром true.
* Вызов setDelay сбрасывает текущую паузу и устанавливает новую.
* @param function anim анимационная функция
* @param interger msc задержка в миллисекундах
* @param boolean continous true - разрешить постоянную паузу, false(default) - отменить постоянную паузу
* @return function результирующая анимационная функция
*/
function AnimationDelay(anim, msc, continous) {
var a=function(animator) {
if(arguments.callee.waittime!=null) {
if(((new Date()).getTime()-arguments.callee.fromTime)<arguments.callee.waittime) return;
if(continous) arguments.callee.setDelay(msc);
else arguments.callee.waittime=null;
}
anim(animator);
}
a.setDelay=function(ms) {
if(typeof(ms)!='number') throw "AnimationDelay.setDelay: wait time '"+ms+"' must be an integer representing time in milliseconds";
this.waittime=ms;
this.fromTime=(new Date()).getTime();
}
a.setDelay(msc);
}
/*==============================- Animation Set -==========================*/
/**
* Обьединение анимаций по времени.
* Иногда(часто =)) требуеться запустить несколько анимаций одновременно. Обьект
* обьединяет анимации в одну. Выход из обьединения произойдёт после того как все
* анимации вызовут либо next(), либо stop(). Если анимация вызвала
* next()/stop(), то она удаляеться из списка анимаций и при следующем проходе
* вызванна не будет.
* @param array масив с функциями-анимациями, что будут выполняться одновременно
* @return function результирующая анимационная функция
*/
function AnimationSet(set) {
return function(anim) {
var animator={}, hasn=false;
for(var i=0; i<set.length; i++) {
if(!set[i]) {
if(i<set.length-1||hasn) continue;
else {anim.next(); return;}
}
hasn=true;
animator.next=animator.stop=function() { set[i]=null; }
animator.wait=function(ms) {
if(!set[i].setDelay) set[i]=AnimationDelay(set[i], ms);
else set[i].setDelay(ms);
}
set[i](animator);
}
}
}
/*=========================- Анимационные функции -=======================*/
/**
* Простая анимация создающая задержку в миллисекундах.
* @param integer ms пауза в миллисекундах
*/
function waitAnimation(ms) {
var w=false;
return function(animator) {
if(w) {animator.next(); w=false;}
animator.wait(ms);
w=true;
}
}
/**
* Анимация изменяющая размер обьекта.
* Изначальный размер обьекта вычисляеться при первом "кадре". Далее эти размеры
* подгоняються под заданные.
* Параметрами задаёться целевой обьект, размеры и время, за которое анимация
* должна выполниться.
* Внимание: время всегда используеться чуть больше чем заданно, т.к. анимация
* на сложных обьектах браузера всё таки жутко не эффективная работа =)
* @param AnimationQueue thread анимационный поток в котором будем исполняться
* @param integer time длительность анимации
* @param HTMLElement obj целевой обьект
* @param integer width требуемая ширина обьекта
* @param integer height требуемая высота обьекта
*/
function resizeAnimation(thread, time, obj, width, height) {
var steps=Math.ceil(time/thread.settings.frameDelay);
var currstep=1;
var owidth, oheight, hstep, wstep;
return function(animator) {
if(typeof(owidth)!='number') {
owidth=obj.offsetWidth;
oheight=obj.offsetHeight;
wstep=(width-owidth)/steps;
hstep=(height-oheight)/steps;
}
if(currstep>steps) {
obj.style.width=width+'px';
obj.style.height=height+'px';
animator.next();
return;
}
obj.style.width=Math.round(owidth+wstep*currstep)+'px';
obj.style.height=Math.round(oheight+hstep*currstep)+'px';
currstep++;
}
}
/**
* Простая анимация "печатаемого текста".
* Заданная строка "печатаеться" через innerHTML в целевом обьекте.
*/
function typeAnimation(obj, text, timedelay) {
var len=0;
return function(animator) {
animator.wait(timedelay);
if(++len>text.length) {len=0; animator.next();}
else obj.innerHTML=text.substring(0, len);
}
}
/*==============================- Задаём анимации -=======================*/
var a, b; //два анимационных потока
function initAnimations() {
a=new AnimationQueue(); //тред для квадратов
a.setLoop(false); //будем крутить только один раз
b=new AnimationQueue(); //а тред для текста бесконечно
//добавляем две анимации, исполняемые друг за другом
a.add(resizeAnimation(a, 1000, document.getElementById('red'), 400, 200));
a.add(resizeAnimation(a, 2500, document.getElementById('green'), 400, 300));
//а эти две анимации будут исполняться параллельно
var pack=[
resizeAnimation(a, 2000, document.getElementById('red'), 10, 10),
resizeAnimation(a, 2000, document.getElementById('green'), 10, 10)
];
a.add(AnimationSet(pack));
//анимации для текста в отдельном потоке
b.add(typeAnimation(document.getElementById('text'), "Текст печаетаеться со скоростью десять букв в секунду", 100));
b.add(typeAnimation(document.getElementById('text'), "А этот со скоростью три буквы в секунду", 300));
b.add(typeAnimation(document.getElementById('text'), "Можно ещё быстрей, пауза всего 50 миллисекунд", 50));
b.add(waitAnimation(1000)); //подождём секунду, замечаем что это тоже анимация ;-)
//теперь весь бутерброд запускаем =)
a.start();
b.start();
}
function stopAnimations() {
a.stop();
b.stop();
a=b=null;
}
//-->
</script>
</head>
<body>
<div id="red"></div>
<div id="green"></div>
<div id="text"></div>
<button onclick="initAnimations()">Start</button>
<button onclick="stopAnimations()">Stop</button>
</body>
</html>