1 /** 
  2  * JET (Javascript Extension Tools) 
  3  * Copyright (c) 2009, KDV.cn, All rights reserved.
  4  * Code licensed under the BSD License:
  5  * http://developer.kdv.cn/jet/license.txt
  6  *
  7  * @fileOverview Jx!
  8  * @version 1.0
  9  * @author  Kinvix(<a href="mailto:Kinvix@gmail.com">Kinvix@gmail.com</a>)
 10  * @description 
 11  * 
 12  */
 13  
 14  
 15 /** 
 16  * @description
 17  * Package: jet.fx
 18  * 
 19  * Need package:
 20  * jet.core.js
 21  * 
 22  */
 23  
 24  
 25 
 26 Jx().$package(function(J){
 27     /**
 28      * 动画包
 29      * @namespace
 30      * @name fx
 31      */
 32     J.fx = J.fx || {};
 33     
 34     var $D = J.dom,
 35         $E = J.event;
 36 
 37     /**
 38      * 动画缓动公式
 39      * 
 40      * Linear:无缓动效果;
 41      * Quadratic:二次方的缓动(t^2);
 42      * Cubic:三次方的缓动(t^3);
 43      * Quartic:四次方的缓动(t^4);
 44      * Quintic:五次方的缓动(t^5);
 45      * Sinusoidal:正弦曲线的缓动(sin(t));
 46      * Exponential:指数曲线的缓动(2^t);
 47      * Circular:圆形曲线的缓动(sqrt(1-t^2));
 48      * Elastic:指数衰减的正弦曲线缓动;
 49      * Back:超过范围的三次方缓动((s+1)*t^3 - s*t^2);
 50      * Bounce:指数衰减的反弹缓动。
 51      * 
 52      * 每个效果都分三个缓动方式(方法),分别是:
 53      * easeIn:从0开始加速的缓动;
 54      * easeOut:减速到0的缓动;
 55      * easeInOut:前半段从0开始加速,后半段减速到0的缓动。
 56      * 其中Linear是无缓动效果,没有以上效果。
 57      * 
 58      * p,pos: current(当前);
 59      * x: value(其他参数);
 60      */
 61     var Transition = new J.Class({
 62         init:function(transition, params){
 63             params = J.array.toArray(params);
 64             
 65             var newTransition =  J.extend(transition, {
 66                 //从0开始加速的缓动;
 67                 easeIn: function(pos){
 68                     return transition(pos, params);
 69                 },
 70                 //减速到0的缓动;
 71                 easeOut: function(pos){
 72                     return 1 - transition(1 - pos, params);
 73                 },
 74                 //前半段从0开始加速,后半段减速到0的缓动。
 75                 easeInOut: function(pos){
 76                     return (pos <= 0.5) ? transition(2 * pos, params) / 2 : (2 - transition(2 * (1 - pos), params)) / 2;
 77                 }
 78             });
 79             
 80             return newTransition;
 81         }
 82     });
 83     
 84     /**
 85      * 
 86      * 动画缓动公式
 87      * 
 88      * Linear:无缓动效果;
 89      * Quadratic:二次方的缓动(t^2);
 90      * Cubic:三次方的缓动(t^3);
 91      * Quartic:四次方的缓动(t^4);
 92      * Quintic:五次方的缓动(t^5);
 93      * Sinusoidal:正弦曲线的缓动(sin(t));
 94      * Exponential:指数曲线的缓动(2^t);
 95      * Circular:圆形曲线的缓动(sqrt(1-t^2));
 96      * Elastic:指数衰减的正弦曲线缓动;
 97      * Back:超过范围的三次方缓动((s+1)*t^3 - s*t^2);
 98      * Bounce:指数衰减的反弹缓动。
 99      * 
100      * 每个效果都分三个缓动方式(方法),分别是:
101      * easeIn:从0开始加速的缓动;
102      * easeOut:减速到0的缓动;
103      * easeInOut:前半段从0开始加速,后半段减速到0的缓动。
104      * 其中Linear是无缓动效果,没有以上效果。
105      * 
106      * p,pos: current(当前);
107      * x: value(其他参数);
108      *  
109      * @memberOf fx
110      * @namespace
111      * @name transitions
112      */
113     var transitions = {
114         /**
115          * linear:无缓动效果;
116          * @memberOf fx.transitions
117          * @function
118          * @name linear
119          */ 
120         linear: function(p){
121             return p;
122         },
123         extend : function(newTransitions){
124             for (var transition in newTransitions){
125                 this[transition] = new Transition(newTransitions[transition]);
126             }
127         }
128     
129     };
130     
131 
132     
133     
134     transitions.extend(
135     /**
136      * @lends fx.transitions
137      */
138     {
139         
140         /**
141          * pow:n次方的缓动(t^n),n默认为6;
142          */
143         pow: function(p, x){
144             return Math.pow(p, x && x[0] || 6);
145         },
146         
147         /**
148          * exponential:指数曲线的缓动(2^t);
149          */
150         exponential: function(p){
151             return Math.pow(2, 8 * (p - 1));
152         },
153         
154         /**
155          * circular:圆形曲线的缓动(sqrt(1-t^2));
156          */
157         circular: function(p){
158             return 1 - Math.sin(Math.acos(p));
159         },
160     
161         /**
162          * sinusoidal:正弦曲线的缓动(sin(t));
163          */
164         sinusoidal: function(p){
165             return 1 - Math.sin((1 - p) * Math.PI / 2);
166         },
167     
168         /**
169          * back:超过范围的三次方缓动((s+1)*t^3 - s*t^2);
170          */
171         back: function(p, x){
172             x = x && x[0] || 1.618;
173             return Math.pow(p, 2) * ((x + 1) * p - x);
174         },
175         
176         /**
177          * bounce:指数衰减的反弹缓动。
178          */
179         bounce: function(p){
180             var value;
181             for (var a = 0, b = 1; 1; a += b, b /= 2){
182                 if (p >= (7 - 4 * a) / 11){
183                     value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
184                     break;
185                 }
186             }
187             return value;
188         },
189     
190         /**
191          * elastic:指数衰减的正弦曲线缓动;
192          */
193         elastic: function(p, x){
194             return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x && x[0] || 1) / 3);
195         }
196     
197     });
198 
199     // quadratic,cubic,quartic,quintic:2-5次方的缓动(t^5);
200     var pows = ['quadratic', 'cubic', 'quartic', 'quintic'];
201     J.array.forEach(pows, function(transition, i){
202         transitions[transition] = new Transition(function(p){
203             return Math.pow(p, [i + 2]);
204         });
205     });
206     
207     
208     
209     /**
210      * 节拍器类
211      * @class 
212      * @memberOf fx
213      * @name Beater
214      * @constructor
215      * 
216      * @param {Number} duration: 节拍时长,单位毫秒
217      * @param {Number} loop: 循环次数,默认为1,0为无限循环
218      * @param {Number} fps: fps你懂的
219      * @return 节拍器实例
220      */
221     var Beater = new J.Class(
222     /**
223      * @lends fx.Beater.prototype
224      */    
225     {
226         /**
227          * @ignore
228          */
229         init : function(option){
230             
231             this.setOption(option);
232         },
233 
234         setOption: function(option){
235             this.option = option = J.extend({
236                 duration:500,
237                 loop:1,
238                 //不建议fps超过62,因为浏览器setInterval方法的限制,最小时间间隔是16ms,所以每秒实际帧速不能超过62帧
239                 fps:1000/(option && option.duration || 500)
240             }, option);
241         },
242         
243         start: function(){
244             if (!this.check()){
245                 return this;
246             }
247             var option = this.option;
248             this.time = 0;
249             this.loop = option.loop;
250             this.onStart.apply(this, arguments);
251             this.startTimer();
252             return this;
253         },
254         
255         pause: function(){
256             this.stopTimer();
257             return this;
258         },
259 
260         resume: function(){
261             this.startTimer();
262             return this;
263         },
264 
265         end: function(){
266             if (this.stopTimer()){
267                 this.onEnd.apply(this, arguments);
268             }
269             return this;
270         },
271 
272         cancel: function(){
273             if (this.stopTimer()){
274                 this.onCancel.apply(this, arguments);
275             }
276             return this;
277         },
278 
279         onStart: function(){
280             $E.notifyObservers(this, "start");
281         },
282 
283         onEnd: function(){
284             $E.notifyObservers(this, "end");
285         },
286 
287         onCancel: function(){
288             $E.notifyObservers(this, "cancel");
289         },
290 
291         onLoop: function(){
292             $E.notifyObservers(this, "loop");
293         },
294 
295         onBeater: function(){
296             $E.notifyObservers(this, "beater");
297         },
298 
299         check: function(){
300             if (!this.timer){
301                 return true;
302             }
303             return false;
304         },
305 
306         setDuration: function(duration){
307             this.option.duration = duration || 500;
308         },
309         
310         update: function(){
311             var that = this,
312                 time = J.now(),
313                 option = that.option;
314             if (time < that.time + that.option.duration){
315                 that.onBeater((time - that.time) / option.duration);
316             } else {
317                 that.onBeater(1);
318                 that.onLoop();
319                 if(option.loop<=0 || --that.loop){
320                     that.time = time;
321                 }else{
322                     that.stopTimer();
323                     that.onEnd();
324                 }
325             }
326         },
327 
328         stopTimer: function(){
329             if (!this.timer){
330                 return false;
331             }
332             this.time = J.now() - this.time;
333             this.timer = removeInstance(this);
334             return true;
335         },
336 
337         startTimer: function(){
338             if (this.timer) return false;
339             this.time = J.now() - this.time;
340             this.timer = addInstance(this);
341             return true;
342         }
343     });
344     
345     var instances = {}, timers = {};
346 
347     var loop = function(list){
348         for (var i = list.length; i--;){
349             if (list[i]){
350                 list[i].update();
351             }
352         }
353     };
354 
355     var addInstance = function(instance){
356         var fps = instance.option.fps,
357             list = instances[fps] || (instances[fps] = []);
358         list.push(instance);
359         if (!timers[fps]){
360             timers[fps] = setInterval(function(){
361                 loop(list);
362             }, Math.round(1000 / fps));
363         }
364         return true;
365     };
366 
367     var removeInstance = function(instance){
368         var fps = instance.option.fps,
369             list = instances[fps] || [];
370         J.array.remove(list,instance);
371         if (!list.length && timers[fps]){
372             timers[fps] = clearInterval(timers[fps]);
373         }
374         return false;
375     };
376     
377     
378     
379     /**
380      * 动画类
381      * @class 
382      * @name Animation
383      * @memberOf fx
384      * @extends fx.Beater
385      * 
386      * @param {Element} element  动画的dom
387      * @param {String} property  css 属性
388      * @param {Mix} from  起始值
389      * @param {Mix} to  到达值
390      * @param {String} unit  单位
391      * @param {Number} duration  动画时长,单位毫秒
392      * @param {Number} loop  循环次数,默认为1,0为无限循环
393      * @param {Function} transition  变化公式
394      * @param {Number} fps  fps你懂的
395      * @param {Function} css属性转换器
396      * @return 动画实例
397      */
398     var Animation = new J.Class({extend:Beater},
399     /**
400      * @lends fx.Animation.prototype
401      */
402     {
403         /**
404          * @ignore
405          */
406         init : function(option){
407             Animation.superClass['init'].apply(this,arguments);
408             this.option = this.option || {};
409             option = J.extend(this.option, {
410                 element: null, 
411                 property: "", 
412                 from: 0, 
413                 to: 1,
414                 unit:false,
415                 transition: transitions.linear,
416                 //不建议fps超过62,因为浏览器setInterval方法的限制,最小时间间隔是16ms,所以每秒实际帧速不能超过62帧
417                 fps:25,
418                 converter:converter
419             }, option);
420             this.from = option.from;
421             this.to = option.to;
422         },
423 
424         getTransition: function(){
425             var transition = this.option.transition || transitions.sinusoidal.easeInOut;
426             return transition;
427         },
428 
429         set: function(now){
430             var option = this.option;
431             this.render(option.element, option.property, now, option.unit);
432             return this;
433         },
434         
435         setFromTo: function(from, to){
436             this.from = from;
437             this.to = to;
438         },
439         
440         render: function(element, property, value, unit){
441             $D.setStyle(element, property, this.option.converter(value,unit));
442         },
443 
444         compute: function(from, to, delta){
445             return compute(from, to, delta);
446         },
447 
448         onStart: function(from,to){
449             var that = this;
450             var option = that.option;
451             that.from = J.isNumber(from) ? from : option.from;
452             that.to = J.isNumber(to) ? to : option.to;
453             $E.notifyObservers(this, "start");
454         },
455 
456         onEnd: function(){
457             var that = this;
458             var delta = that.option.transition(1);
459             that.set(that.compute(that.from, that.to, delta));
460             $E.notifyObservers(this, "end");
461         },
462 
463         onCancel: function(){
464             var that = this;
465             var delta = that.option.transition(0);
466             that.set(that.compute(that.from, that.to, delta));
467             $E.notifyObservers(this, "cancel");
468         },
469 
470         onBeater: function(progress){
471             var that = this;
472             var delta = that.option.transition(progress);
473             that.set(that.compute(that.from, that.to, delta));
474         }
475     });
476     
477     var compute = function(from, to, delta){
478         return (to - from) * delta + from;
479     };
480 
481     var converter = function(value,unit){
482         return (unit) ? value + unit : value;
483     };
484     
485     /**
486      * TODO 这是原来的动画类, peli改了原来的动画类,抽出了节拍器, 
487      * 但是新的动画类有问题,但是我没时间改, 就先用着旧的
488      * by az , 2011-3-28
489      * @ignore
490      * 
491      * @param {Element} element: 动画的dom
492      * @param {String} property: css 属性
493      * @param {Mix} from: 起始值
494      * @param {Mix} to: 到达值
495      * @param {String} unit: 单位
496      * @param {Number} duration: 动画时长,单位毫秒
497      * @param {Function} transition: 变化公式
498      * @param {Number} fps: fps你懂的
499      * 
500      * @return 动画实例
501      */
502     var Animation2 = new J.Class({
503         init : function(option){
504             //el, style, begin, end, fx, total
505             this.option = option = J.extend({
506                 element: null, 
507                 property: "", 
508                 from: 0, 
509                 to: 0,
510                 unit:false,
511                 duration:500,
512                 transition: transitions.linear,
513                 //不建议fps超过62,因为浏览器setInterval方法的限制,最小时间间隔是16ms,所以每秒实际帧速不能超过62帧
514                 fps:25
515             }, option);
516             
517             this.from = option.from;
518             this.to = option.to;
519             
520 
521         },
522 
523         
524         getTransition: function(){
525             var transition = this.option.transition || transitions.sinusoidal.easeInOut;
526             return transition;
527         },
528         update: function(){
529             var that = this,
530                 time = J.now();
531             var option = that.option;
532             if (time < that.time + that.option.duration){
533                 var delta = option.transition((time - that.time) / option.duration);
534                 that.set(that.compute(that.from, that.to, delta));
535             } else {
536                 that.set(that.compute(that.from, that.to, 1));
537                 that.end();
538                 
539             }
540         },
541 
542         set: function(now){
543             var option = this.option;
544             this.render(option.element, option.property, now, option.unit);
545             return this;
546         },
547         
548         setDuration: function(duration){
549             this.option.duration = duration || 500;
550         },
551         
552         setFromTo: function(from, to){
553             this.from = from;
554             this.to = to;
555         },
556         
557         render: function(element, property, value, unit){
558             value = (unit) ? value + unit : value;
559             $D.setStyle(element, property, value);
560         },
561 
562         compute: function(from, to, delta){
563             return compute(from, to, delta);
564         },
565 
566         check: function(){
567             if (!this.timer){
568                 return true;
569             }
570             return false;
571         },
572 
573         start: function(from, to){
574             var that = this;
575             if (!that.check(from, to)){
576                 return that;
577             }
578             var option = that.option;
579             that.from = J.isNumber(from) ? from : option.from;
580             that.to = J.isNumber(to) ? to : option.to;
581             
582             that.time = 0;
583             that.startTimer();
584             that.onStart();
585             
586             return that;
587         },
588 
589         end: function(){
590             if (this.stopTimer()){
591                 this.onEnd();
592             }
593             return this;
594         },
595 
596         cancel: function(){
597             if (this.stopTimer()){
598                 this.onCancel();
599             }
600             return this;
601         },
602 
603         onStart: function(){
604             $E.notifyObservers(this, "start");
605         },
606 
607         onEnd: function(){
608             $E.notifyObservers(this, "end");
609         },
610 
611         onCancel: function(){
612             $E.notifyObservers(this, "cancel");
613         },
614 
615         pause: function(){
616             this.stopTimer();
617             return this;
618         },
619 
620         resume: function(){
621             this.startTimer();
622             return this;
623         },
624 
625         stopTimer: function(){
626             if (!this.timer){
627                 return false;
628             }
629             this.time = J.now() - this.time;
630             this.timer = removeInstance(this);
631             return true;
632         },
633 
634         startTimer: function(){
635             if (this.timer) return false;
636             this.time = J.now() - this.time;
637             this.timer = addInstance(this);
638             return true;
639         }
640     });
641     
642     J.fx.Beater = Beater;
643     J.fx.Animation = Animation;
644     J.fx.Animation2 = Animation2;
645     J.fx.transitions = transitions;
646 });
647 
648 
649 
650 
651 
652 
653 
654 
655 
656 
657 
658 
659 
660