/*
---
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 () {
/**
* @Fx: 本类一般不独立使用,它用来提供作为Fx系的类的基础功能类.所有其他的Fx系列的类都继承本类.
**/
var Fx = this.Fx = new Class({
Implements: [Chain, Events, Options],
// #region - constructor -
/**
* @Events:
* @event start - (function) 特效开始执行时触发
* @event cancel - (function) 手动停止特效执行时触发
* @event complete - (function) 特效执行完成后触发
* @event chainComplete - (function) 当使用link可选项为'chain'时, 该事件在特效链执行完后触发
* @event stop - (function) 特效执行完成前,执行stop方法时触发
**/
/**
* @Optoins:
* @option fps - (number: 默认为 60) 动画特效的秒帧数
* @option unit - (string: 默认为 false) 计量单位(如: 'px', 'em', 或 '%').
* @option duration - (number: 默认为 500) 可以让你定义这个动画的持续时间。持续时间和速度是不一样的,因此如果你想让一个对象在一秒内移动100个像素,
* 那么它将比一个每秒移动1000个像素的对象要慢。你可以输入一个数字(以毫秒为单位). 也可使用以下预定义字符串:
* 'short' - 250ms
* 'normal' - 500ms
* 'long' - 1000ms
* @option frames - (number) 设定动画特效执行的总帧数,默认为null自动匹配
* @option frameSkip - (boolean: 默认为true) 设定动画特效当一帧执行的时间大于每帧之间的时间间隔,是否跳过这段时间所要执行的帧
* @option link - (string: 默认为 ignore) 可为: 'ignore', 'cancel' 或 'chain'
* 'ignore' - 当特效正在执行之中时,再次调用特效开始的方法将被忽略(和可选项'wait'为true时同义)
* 'cancel' - 当特效正在执行之中时,再次调用特效开始的方法将立即取消当前执行的特效,开始执行新的特效
* 'chain' - 当特效正在执行之中时,再次调用特效开始的方法将会把新的特效链接在当前执行的特效之后,依次执行各个特效
* @option transition - (function: 默认为 Fx.Transitions.Sine.easeInOut) 特效的变换方程, 详见Fx.Transitions. 也可以使用如下格式的字符串:
* transition[:in][:out] - 例如: 'linear', 'quad:in', 'back:in', 'bounce:out', 'elastic:out', 'sine:in:out'
**/
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);
},
// #endregion
/**
* @method: getTransition
* @returns: (function) - 特效的变换方程
* @description: 取得动画特效所要执行的特效方程
**/
getTransition: function () {
return function (p) {
return -(Math.cos(Math.PI * p) - 1) / 2;
};
},
/**
* @method: step
* @param now - (mixed) 特效值
* @returns: (function) - 特效的变换方程
* @description: 动画特效每一步执行的操作
**/
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();
}
},
/**
* @method: set
* @param value - (mixed) 特效值
* @description: 用于设置特效值.该方法在特效变换过程中每个'步进'都会调用; 也可以手工调用,留作派生类实现
**/
set: function (now) {
return now;
},
/**
* @method: compute
* @param from - (mixed) 特效的起始值
* @param to - (mixed) 特效的结束值
* @param delta - (mixed) 特效变化所需要的比例因子
* @description: 根据初始值,结束值和比例因子求目标值
**/
compute: function (from, to, delta) {
return Fx.compute(from, to, delta);
},
/**
* @method: check
* @parameters - 与start方法参数一致
* @returns: (boolean) - 如果start方法可以继续执行, 则返回 true ; 否则返回 false
* @description: 判断当特效正在执行之中时,再次调用特效开始的方法(start)是否继续可以执行start方法
**/
check: function () {
// 如果特效没有运行,返回true
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;
},
/**
* @method: start
* @param from - (mixed) 特效的起始值. 如果只给出一个参数,则本值将作为结束值
* @param to - (mixed, 可选) 特效的结束值
* @returns: (object) - 当前的Fx实例
* @description: 开始执行特效变换(并触发'start'事件)
**/
start: function (from, to) {
// 检测start方法是否可以继续执行
if (!this.check(from, to)) { return this; }
/**
# 苦苦的苦瓜
# 2011-09-25
# 将用局部变量_options代替this.options
**/
var _options = this.options;
this.from = from;
this.to = to;
this.frame = (_options.frameSkip) ? 0 : -1;
this.time = null;
// 取得特效执行的变换方程
this.transition = this.getTransition();
var frames = _options.frames,
fps = _options.fps,
duration = _options.duration;
// 可选参数duration既可以数字类型,也可以为字符串类型
this.duration = Fx.Durations[duration] || duration.toInt();
// 取得动画特效每帧之间的时间间隔,毫秒为单位
this.frameInterval = 1000 / fps;
// 计算动画特效执行的总帧数
this.frames = frames || Math.round(this.duration / this.frameInterval);
// 触发'start'事件
this.fireEvent('start', this.subject);
pushInstance.call(this, fps);
return this;
},
/**
* @method: stop
* @returns: (object) - 当前的Fx实例
* @description: 停止一个特效的执行
**/
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;
},
/**
* @method: cancel
* @returns: (object) - 当前的Fx实例
* @description: 取消一个特效的执行(并触发'cancel'事件)
**/
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;
},
/**
* @method: pause
* @returns: (object) - 当前的Fx实例
* @description: 暂停当前执行的特效
**/
pause: function () {
if (this.isRunning()) {
this.time = null;
pullInstance.call(this, this.options.fps);
}
return this;
},
/**
* @method: pause
* @return: (object) - 当前的Fx实例
* @description: 恢复执行暂停中的特效
* @remark: 只有对暂停中的特效执行本方法才有效果, 否则将忽略.
**/
resume: function () {
if ((this.frame < this.frames) && !this.isRunning()) {
pushInstance.call(this, this.options.fps);
}
return this;
},
/**
* @method: isRunning
* @return: (boolean) 特效运行状态
* @description: 检测特效是否正在运行
**/
isRunning: function () {
var list = instances[this.options.fps];
return list && list.contains(this);
}
});
Fx.compute = function (from, to, delta) {
return (to - from) * delta + from;
};
// 预置特效间隔毫秒数,当可选参数duration为字符串时调用Fx.Durations对象得到特效间隔毫秒数
Fx.Durations = { 'short': 250, 'normal': 500, 'long': 1000 };
// global timers
// instances对象字面量缓存所有的fx实例对象,它的所有键值对中的键就是对应一个fps值,值为一个包含设置了相同fps的动画特效实例的数组
// timers对象缓存setInterval()方法返回的ID值
var instances = {},
timers = {};
/**
* @method: loop
* @description: 遍历动画特效实例数组,执行动画特效
**/
var loop = function () {
var now = Date.now();
for (var i = this.length; i--; ) {
var instance = this[i];
if (instance) { instance.step(now); }
}
};
/**
* @method: pushInstance
* @description: 遍历动画特效实例数组,执行动画特效
**/
var pushInstance = function (fps) {
// 取得缓存的与参数fps对应的动画特效数组,如果没有则instances对象新建一个键值存储这个数组
var list = instances[fps] || (instances[fps] = []);
// 缓存fx实例对象
list.push(this);
if (!timers[fps]) {
// 设置定时器
timers[fps] = loop.periodical(Math.round(1000 / fps), list);
}
};
/**
* @method: pullInstance
* @param from - (number) 要停止的动画特效实例的秒帧数
* @description: 停止运行一个动画特效实例
**/
var pullInstance = function (fps) {
// 取得缓存的与参数fps对应的动画特效数组
var list = instances[fps];
if (list) {
// 从数组中删除运行pullInstance函数的fx实例对象
list.erase(this);
if (!list.length && timers[fps]) {
// 如果数组为空,则删除instances对象中的这个数组
delete instances[fps];
// 清除定时器
timers[fps] = clearInterval(timers[fps]);
}
}
};
})();