<!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>