/* --- name: Fx description: Contains the basic animation logic to be extended by all other Fx Classes. license: MIT-style license. requires: [Chain, Events, Options] provides: Fx ... */ (function(){ var Fx = this.Fx = new Class({ Implements: [Chain, Events, Options], options: { /* onStart: nil, onCancel: nil, onComplete: nil, */ fps: 60, unit: false, duration: 500, frames: null, frameSkip: true, link: 'ignore' }, initialize: function(options){ this.subject = this.subject || this; this.setOptions(options); }, getTransition: function(){ return function(p){ return -(Math.cos(Math.PI * p) - 1) / 2; }; }, step: function(now){ if (this.options.frameSkip){ var diff = (this.time != null) ? (now - this.time) : 0, frames = diff / this.frameInterval; this.time = now; this.frame += frames; } else { this.frame++; } if (this.frame < this.frames){ var delta = this.transition(this.frame / this.frames); this.set(this.compute(this.from, this.to, delta)); } else { this.frame = this.frames; this.set(this.compute(this.from, this.to, 1)); this.stop(); } }, set: function(now){ return now; }, compute: function(from, to, delta){ return Fx.compute(from, to, delta); }, check: function(){ if (!this.isRunning()) return true; switch (this.options.link){ case 'cancel': this.cancel(); return true; case 'chain': this.chain(this.caller.pass(arguments, this)); return false; } return false; }, start: function(from, to){ if (!this.check(from, to)) return this; this.from = from; this.to = to; this.frame = (this.options.frameSkip) ? 0 : -1; this.time = null; this.transition = this.getTransition(); var frames = this.options.frames, fps = this.options.fps, duration = this.options.duration; this.duration = Fx.Durations[duration] || duration.toInt(); this.frameInterval = 1000 / fps; this.frames = frames || Math.round(this.duration / this.frameInterval); this.fireEvent('start', this.subject); pushInstance.call(this, fps); return this; }, stop: function(){ if (this.isRunning()){ this.time = null; pullInstance.call(this, this.options.fps); if (this.frames == this.frame){ this.fireEvent('complete', this.subject); if (!this.callChain()) this.fireEvent('chainComplete', this.subject); } else { this.fireEvent('stop', this.subject); } } return this; }, cancel: function(){ if (this.isRunning()){ this.time = null; pullInstance.call(this, this.options.fps); this.frame = this.frames; this.fireEvent('cancel', this.subject).clearChain(); } return this; }, pause: function(){ if (this.isRunning()){ this.time = null; pullInstance.call(this, this.options.fps); } return this; }, resume: function(){ if ((this.frame < this.frames) && !this.isRunning()) pushInstance.call(this, this.options.fps); return this; }, isRunning: function(){ var list = instances[this.options.fps]; return list && list.contains(this); } }); Fx.compute = function(from, to, delta){ return (to - from) * delta + from; }; Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000}; // global timers var instances = {}, timers = {}; var loop = function(){ var now = Date.now(); for (var i = this.length; i--;){ var instance = this[i]; if (instance) instance.step(now); } }; var pushInstance = function(fps){ var list = instances[fps] || (instances[fps] = []); list.push(this); if (!timers[fps]) timers[fps] = loop.periodical(Math.round(1000 / fps), list); }; var pullInstance = function(fps){ var list = instances[fps]; if (list){ list.erase(this); if (!list.length && timers[fps]){ delete instances[fps]; timers[fps] = clearInterval(timers[fps]); } } }; })();