Mootools1.4 - Fx源码分析,如果理解有误欢迎指正:
1 /*
2 ---
3
4 name: Fx
5
6 description: Contains the basic animation logic to be extended by all other Fx Classes.
7
8 license: MIT-style license.
9
10 requires: [Chain, Events, Options]
11
12 provides: Fx
13
14 源码分析: 苦苦的苦瓜(http://www.cnblogs.com/hmking)
15 ...
16 */
17
18 (function () {
19
20 /**
21 * @Fx: 本类一般不独立使用,它用来提供作为Fx系的类的基础功能类.所有其他的Fx系列的类都继承本类.
22 **/
23 var Fx = this.Fx = new Class({
24
25 Implements: [Chain, Events, Options],
26
27 // #region - constructor -
28
29 /**
30 * @Events:
31 * @event start - (function) 特效开始执行时触发
32 * @event cancel - (function) 手动停止特效执行时触发
33 * @event complete - (function) 特效执行完成后触发
34 * @event chainComplete - (function) 当使用link可选项为'chain'时, 该事件在特效链执行完后触发
35 * @event stop - (function) 特效执行完成前,执行stop方法时触发
36 **/
37
38 /**
39 * @Optoins:
40 * @option fps - (number: 默认为 60) 动画特效的秒帧数
41 * @option unit - (string: 默认为 false) 计量单位(如: 'px', 'em', 或 '%').
42 * @option duration - (number: 默认为 500) 可以让你定义这个动画的持续时间。持续时间和速度是不一样的,因此如果你想让一个对象在一秒内移动100个像素,
43 * 那么它将比一个每秒移动1000个像素的对象要慢。你可以输入一个数字(以毫秒为单位). 也可使用以下预定义字符串:
44 * 'short' - 250ms
45 * 'normal' - 500ms
46 * 'long' - 1000ms
47 * @option frames - (number) 设定动画特效执行的总帧数,默认为null自动匹配
48 * @option frameSkip - (boolean: 默认为true) 设定动画特效当一帧执行的时间大于每帧之间的时间间隔,是否跳过这段时间所要执行的帧
49 * @option link - (string: 默认为 ignore) 可为: 'ignore', 'cancel' 或 'chain'
50 * 'ignore' - 当特效正在执行之中时,再次调用特效开始的方法将被忽略(和可选项'wait'为true时同义)
51 * 'cancel' - 当特效正在执行之中时,再次调用特效开始的方法将立即取消当前执行的特效,开始执行新的特效
52 * 'chain' - 当特效正在执行之中时,再次调用特效开始的方法将会把新的特效链接在当前执行的特效之后,依次执行各个特效
53 * @option transition - (function: 默认为 Fx.Transitions.Sine.easeInOut) 特效的变换方程, 详见Fx.Transitions. 也可以使用如下格式的字符串:
54 * transition[:in][:out] - 例如: 'linear', 'quad:in', 'back:in', 'bounce:out', 'elastic:out', 'sine:in:out'
55 **/
56 options: {
57 /*
58 onStart: nil,
59 onCancel: nil,
60 onComplete: nil,
61 */
62 fps: 60,
63 unit: false,
64 duration: 500,
65 frames: null,
66 frameSkip: true,
67 link: 'ignore'
68 },
69
70 initialize: function (options) {
71 this.subject = this.subject || this;
72 this.setOptions(options);
73 },
74
75 // #endregion
76
77 /**
78 * @method: getTransition
79 * @returns: (function) - 特效的变换方程
80 * @description: 取得动画特效所要执行的特效方程
81 **/
82 getTransition: function () {
83 return function (p) {
84 return -(Math.cos(Math.PI * p) - 1) / 2;
85 };
86 },
87
88 /**
89 * @method: step
90 * @param now - (mixed) 特效值
91 * @returns: (function) - 特效的变换方程
92 * @description: 动画特效每一步执行的操作
93 **/
94 step: function (now) {
95 if (this.options.frameSkip) {
96 // 先取得当前时间减去上一帧执行时的时间,得到两帧之间的时间间隔,计算这段时间内按正常的帧间隔时间能执行的帧的数量
97 var diff = (this.time != null) ? (now - this.time) : 0,
98 frames = diff / this.frameInterval;
99 // 存储当前帧执行时的时间
100 this.time = now;
101 // 执行的帧数累加
102 this.frame += frames;
103 } else {
104 this.frame++;
105 }
106
107 // 判断当前帧是否为动画特效的最后一帧
108 if (this.frame < this.frames) {
109 // 通过特效方程计算动画特效运行当前帧所要变化的比例因子
110 var delta = this.transition(this.frame / this.frames);
111 this.set(this.compute(this.from, this.to, delta));
112 } else {
113 // 动画特效执行完毕
114 this.frame = this.frames;
115 this.set(this.compute(this.from, this.to, 1));
116 this.stop();
117 }
118 },
119
120 /**
121 * @method: set
122 * @param value - (mixed) 特效值
123 * @description: 用于设置特效值.该方法在特效变换过程中每个'步进'都会调用; 也可以手工调用,留作派生类实现
124 **/
125 set: function (now) {
126 return now;
127 },
128
129 /**
130 * @method: compute
131 * @param from - (mixed) 特效的起始值
132 * @param to - (mixed) 特效的结束值
133 * @param delta - (mixed) 特效变化所需要的比例因子
134 * @description: 根据初始值,结束值和比例因子求目标值
135 **/
136 compute: function (from, to, delta) {
137 return Fx.compute(from, to, delta);
138 },
139
140 /**
141 * @method: check
142 * @parameters - 与start方法参数一致
143 * @returns: (boolean) - 如果start方法可以继续执行, 则返回 true ; 否则返回 false
144 * @description: 判断当特效正在执行之中时,再次调用特效开始的方法(start)是否继续可以执行start方法
145 **/
146 check: function () {
147 // 如果特效没有运行,返回true
148 if (!this.isRunning()) { return true; }
149 switch (this.options.link) {
150 case 'cancel': // 不等待正在运行的特效,直接取消并重新开始
151 this.cancel();
152 return true;
153
154 case 'chain': // 等待当前特效运行结束后再继续运行新特效
155 this.chain(this.caller.pass(arguments, this));
156 return false;
157 }
158 return false;
159 },
160
161 /**
162 * @method: start
163 * @param from - (mixed) 特效的起始值. 如果只给出一个参数,则本值将作为结束值
164 * @param to - (mixed, 可选) 特效的结束值
165 * @returns: (object) - 当前的Fx实例
166 * @description: 开始执行特效变换(并触发'start'事件)
167 **/
168 start: function (from, to) {
169 // 检测start方法是否可以继续执行
170 if (!this.check(from, to)) { return this; }
171
172 /**
173 # 苦苦的苦瓜
174 # 2011-09-25
175 # 将用局部变量_options代替this.options
176 **/
177 var _options = this.options;
178
179 this.from = from;
180 this.to = to;
181 this.frame = (_options.frameSkip) ? 0 : -1;
182 this.time = null;
183 // 取得特效执行的变换方程
184 this.transition = this.getTransition();
185 var frames = _options.frames,
186 fps = _options.fps,
187 duration = _options.duration;
188 // 可选参数duration既可以数字类型,也可以为字符串类型
189 this.duration = Fx.Durations[duration] || duration.toInt();
190 // 取得动画特效每帧之间的时间间隔,毫秒为单位
191 this.frameInterval = 1000 / fps;
192 // 计算动画特效执行的总帧数
193 this.frames = frames || Math.round(this.duration / this.frameInterval);
194 // 触发'start'事件
195 this.fireEvent('start', this.subject);
196 pushInstance.call(this, fps);
197 return this;
198 },
199
200 /**
201 * @method: stop
202 * @returns: (object) - 当前的Fx实例
203 * @description: 停止一个特效的执行
204 **/
205 stop: function () {
206 if (this.isRunning()) {
207 this.time = null;
208 pullInstance.call(this, this.options.fps);
209 if (this.frames == this.frame) {
210 this.fireEvent('complete', this.subject);
211 if (!this.callChain()) {
212 this.fireEvent('chainComplete', this.subject);
213 }
214 } else {
215 this.fireEvent('stop', this.subject);
216 }
217 }
218 return this;
219 },
220
221 /**
222 * @method: cancel
223 * @returns: (object) - 当前的Fx实例
224 * @description: 取消一个特效的执行(并触发'cancel'事件)
225 **/
226 cancel: function () {
227 if (this.isRunning()) {
228 this.time = null;
229 pullInstance.call(this, this.options.fps);
230 this.frame = this.frames;
231 this.fireEvent('cancel', this.subject).clearChain();
232 }
233 return this;
234 },
235
236 /**
237 * @method: pause
238 * @returns: (object) - 当前的Fx实例
239 * @description: 暂停当前执行的特效
240 **/
241 pause: function () {
242 if (this.isRunning()) {
243 this.time = null;
244 pullInstance.call(this, this.options.fps);
245 }
246 return this;
247 },
248
249 /**
250 * @method: pause
251 * @return: (object) - 当前的Fx实例
252 * @description: 恢复执行暂停中的特效
253 * @remark: 只有对暂停中的特效执行本方法才有效果, 否则将忽略.
254 **/
255 resume: function () {
256 if ((this.frame < this.frames) && !this.isRunning()) {
257 pushInstance.call(this, this.options.fps);
258 }
259 return this;
260 },
261
262 /**
263 * @method: isRunning
264 * @return: (boolean) 特效运行状态
265 * @description: 检测特效是否正在运行
266 **/
267 isRunning: function () {
268 var list = instances[this.options.fps];
269 return list && list.contains(this);
270 }
271
272 });
273
274 Fx.compute = function (from, to, delta) {
275 return (to - from) * delta + from;
276 };
277
278 // 预置特效间隔毫秒数,当可选参数duration为字符串时调用Fx.Durations对象得到特效间隔毫秒数
279 Fx.Durations = { 'short': 250, 'normal': 500, 'long': 1000 };
280
281 // global timers
282 // instances对象字面量缓存所有的fx实例对象,它的所有键值对中的键就是对应一个fps值,值为一个包含设置了相同fps的动画特效实例的数组
283 // timers对象缓存setInterval()方法返回的ID值
284 var instances = {},
285 timers = {};
286
287 /**
288 * @method: loop
289 * @description: 遍历动画特效实例数组,执行动画特效
290 **/
291 var loop = function () {
292 var now = Date.now();
293 for (var i = this.length; i--; ) {
294 var instance = this[i];
295 if (instance) { instance.step(now); }
296 }
297 };
298
299 /**
300 * @method: pushInstance
301 * @description: 遍历动画特效实例数组,执行动画特效
302 **/
303 var pushInstance = function (fps) {
304 // 取得缓存的与参数fps对应的动画特效数组,如果没有则instances对象新建一个键值存储这个数组
305 var list = instances[fps] || (instances[fps] = []);
306 // 缓存fx实例对象
307 list.push(this);
308 if (!timers[fps]) {
309 // 设置定时器
310 timers[fps] = loop.periodical(Math.round(1000 / fps), list);
311 }
312 };
313
314 /**
315 * @method: pullInstance
316 * @param from - (number) 要停止的动画特效实例的秒帧数
317 * @description: 停止运行一个动画特效实例
318 **/
319 var pullInstance = function (fps) {
320 // 取得缓存的与参数fps对应的动画特效数组
321 var list = instances[fps];
322 if (list) {
323 // 从数组中删除运行pullInstance函数的fx实例对象
324 list.erase(this);
325 if (!list.length && timers[fps]) {
326 // 如果数组为空,则删除instances对象中的这个数组
327 delete instances[fps];
328 // 清除定时器
329 timers[fps] = clearInterval(timers[fps]);
330 }
331 }
332 };
333
334 })();