source: Dev/trunk/src/client/dojo/_base/fx.js

Last change on this file was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 19.1 KB
Line 
1define(["./kernel", "./config", /*===== "./declare", =====*/ "./lang", "../Evented", "./Color", "../aspect", "../sniff", "../dom", "../dom-style"],
2        function(dojo, config, /*===== declare, =====*/ lang, Evented, Color, aspect, has, dom, style){
3        // module:
4        //              dojo/_base/fx
5        // notes:
6        //              Animation loosely package based on Dan Pupius' work, contributed under CLA; see
7        //              http://pupius.co.uk/js/Toolkit.Drawing.js
8
9        var _mixin = lang.mixin;
10
11        // Module export
12        var basefx = {
13                // summary:
14                //              This module defines the base dojo/_base/fx implementation.
15        };
16
17        var _Line = basefx._Line = function(/*int*/ start, /*int*/ end){
18                // summary:
19                //              Object used to generate values from a start value to an end value
20                // start: int
21                //              Beginning value for range
22                // end: int
23                //              Ending value for range
24                this.start = start;
25                this.end = end;
26        };
27
28        _Line.prototype.getValue = function(/*float*/ n){
29                // summary:
30                //              Returns the point on the line
31                // n:
32                //              a floating point number greater than 0 and less than 1
33                return ((this.end - this.start) * n) + this.start; // Decimal
34        };
35
36        var Animation = basefx.Animation = function(args){
37                // summary:
38                //              A generic animation class that fires callbacks into its handlers
39                //              object at various states.
40                // description:
41                //              A generic animation class that fires callbacks into its handlers
42                //              object at various states. Nearly all dojo animation functions
43                //              return an instance of this method, usually without calling the
44                //              .play() method beforehand. Therefore, you will likely need to
45                //              call .play() on instances of `Animation` when one is
46                //              returned.
47                // args: Object
48                //              The 'magic argument', mixing all the properties into this
49                //              animation instance.
50
51                _mixin(this, args);
52                if(lang.isArray(this.curve)){
53                        this.curve = new _Line(this.curve[0], this.curve[1]);
54                }
55
56        };
57        Animation.prototype = new Evented();
58
59        lang.extend(Animation, {
60                // duration: Integer
61                //              The time in milliseconds the animation will take to run
62                duration: 350,
63
64        /*=====
65                // curve: _Line|Array
66                //              A two element array of start and end values, or a `_Line` instance to be
67                //              used in the Animation.
68                curve: null,
69
70                // easing: Function?
71                //              A Function to adjust the acceleration (or deceleration) of the progress
72                //              across a _Line
73                easing: null,
74        =====*/
75
76                // repeat: Integer?
77                //              The number of times to loop the animation
78                repeat: 0,
79
80                // rate: Integer?
81                //              the time in milliseconds to wait before advancing to next frame
82                //              (used as a fps timer: 1000/rate = fps)
83                rate: 20 /* 50 fps */,
84
85        /*=====
86                // delay: Integer?
87                //              The time in milliseconds to wait before starting animation after it
88                //              has been .play()'ed
89                delay: null,
90
91                // beforeBegin: Event?
92                //              Synthetic event fired before a Animation begins playing (synchronous)
93                beforeBegin: null,
94
95                // onBegin: Event?
96                //              Synthetic event fired as a Animation begins playing (useful?)
97                onBegin: null,
98
99                // onAnimate: Event?
100                //              Synthetic event fired at each interval of the Animation
101                onAnimate: null,
102
103                // onEnd: Event?
104                //              Synthetic event fired after the final frame of the Animation
105                onEnd: null,
106
107                // onPlay: Event?
108                //              Synthetic event fired any time the Animation is play()'ed
109                onPlay: null,
110
111                // onPause: Event?
112                //              Synthetic event fired when the Animation is paused
113                onPause: null,
114
115                // onStop: Event
116                //              Synthetic event fires when the Animation is stopped
117                onStop: null,
118
119        =====*/
120
121                _percent: 0,
122                _startRepeatCount: 0,
123
124                _getStep: function(){
125                        var _p = this._percent,
126                                _e = this.easing
127                        ;
128                        return _e ? _e(_p) : _p;
129                },
130                _fire: function(/*Event*/ evt, /*Array?*/ args){
131                        // summary:
132                        //              Convenience function.  Fire event "evt" and pass it the
133                        //              arguments specified in "args".
134                        // description:
135                        //              Convenience function.  Fire event "evt" and pass it the
136                        //              arguments specified in "args".
137                        //              Fires the callback in the scope of this Animation
138                        //              instance.
139                        // evt:
140                        //              The event to fire.
141                        // args:
142                        //              The arguments to pass to the event.
143                        var a = args||[];
144                        if(this[evt]){
145                                if(config.debugAtAllCosts){
146                                        this[evt].apply(this, a);
147                                }else{
148                                        try{
149                                                this[evt].apply(this, a);
150                                        }catch(e){
151                                                // squelch and log because we shouldn't allow exceptions in
152                                                // synthetic event handlers to cause the internal timer to run
153                                                // amuck, potentially pegging the CPU. I'm not a fan of this
154                                                // squelch, but hopefully logging will make it clear what's
155                                                // going on
156                                                console.error("exception in animation handler for:", evt);
157                                                console.error(e);
158                                        }
159                                }
160                        }
161                        return this; // Animation
162                },
163
164                play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){
165                        // summary:
166                        //              Start the animation.
167                        // delay:
168                        //              How many milliseconds to delay before starting.
169                        // gotoStart:
170                        //              If true, starts the animation from the beginning; otherwise,
171                        //              starts it from its current position.
172                        // returns: Animation
173                        //              The instance to allow chaining.
174
175                        var _t = this;
176                        if(_t._delayTimer){ _t._clearTimer(); }
177                        if(gotoStart){
178                                _t._stopTimer();
179                                _t._active = _t._paused = false;
180                                _t._percent = 0;
181                        }else if(_t._active && !_t._paused){
182                                return _t;
183                        }
184
185                        _t._fire("beforeBegin", [_t.node]);
186
187                        var de = delay || _t.delay,
188                                _p = lang.hitch(_t, "_play", gotoStart);
189
190                        if(de > 0){
191                                _t._delayTimer = setTimeout(_p, de);
192                                return _t;
193                        }
194                        _p();
195                        return _t;      // Animation
196                },
197
198                _play: function(gotoStart){
199                        var _t = this;
200                        if(_t._delayTimer){ _t._clearTimer(); }
201                        _t._startTime = new Date().valueOf();
202                        if(_t._paused){
203                                _t._startTime -= _t.duration * _t._percent;
204                        }
205
206                        _t._active = true;
207                        _t._paused = false;
208                        var value = _t.curve.getValue(_t._getStep());
209                        if(!_t._percent){
210                                if(!_t._startRepeatCount){
211                                        _t._startRepeatCount = _t.repeat;
212                                }
213                                _t._fire("onBegin", [value]);
214                        }
215
216                        _t._fire("onPlay", [value]);
217
218                        _t._cycle();
219                        return _t; // Animation
220                },
221
222                pause: function(){
223                        // summary:
224                        //              Pauses a running animation.
225                        var _t = this;
226                        if(_t._delayTimer){ _t._clearTimer(); }
227                        _t._stopTimer();
228                        if(!_t._active){ return _t; /*Animation*/ }
229                        _t._paused = true;
230                        _t._fire("onPause", [_t.curve.getValue(_t._getStep())]);
231                        return _t; // Animation
232                },
233
234                gotoPercent: function(/*Decimal*/ percent, /*Boolean?*/ andPlay){
235                        // summary:
236                        //              Sets the progress of the animation.
237                        // percent:
238                        //              A percentage in decimal notation (between and including 0.0 and 1.0).
239                        // andPlay:
240                        //              If true, play the animation after setting the progress.
241                        var _t = this;
242                        _t._stopTimer();
243                        _t._active = _t._paused = true;
244                        _t._percent = percent;
245                        if(andPlay){ _t.play(); }
246                        return _t; // Animation
247                },
248
249                stop: function(/*boolean?*/ gotoEnd){
250                        // summary:
251                        //              Stops a running animation.
252                        // gotoEnd:
253                        //              If true, the animation will end.
254                        var _t = this;
255                        if(_t._delayTimer){ _t._clearTimer(); }
256                        if(!_t._timer){ return _t; /* Animation */ }
257                        _t._stopTimer();
258                        if(gotoEnd){
259                                _t._percent = 1;
260                        }
261                        _t._fire("onStop", [_t.curve.getValue(_t._getStep())]);
262                        _t._active = _t._paused = false;
263                        return _t; // Animation
264                },
265
266                status: function(){
267                        // summary:
268                        //              Returns a string token representation of the status of
269                        //              the animation, one of: "paused", "playing", "stopped"
270                        if(this._active){
271                                return this._paused ? "paused" : "playing"; // String
272                        }
273                        return "stopped"; // String
274                },
275
276                _cycle: function(){
277                        var _t = this;
278                        if(_t._active){
279                                var curr = new Date().valueOf();
280                                // Allow durations of 0 (instant) by setting step to 1 - see #13798
281                                var step = _t.duration === 0 ? 1 : (curr - _t._startTime) / (_t.duration);
282
283                                if(step >= 1){
284                                        step = 1;
285                                }
286                                _t._percent = step;
287
288                                // Perform easing
289                                if(_t.easing){
290                                        step = _t.easing(step);
291                                }
292
293                                _t._fire("onAnimate", [_t.curve.getValue(step)]);
294
295                                if(_t._percent < 1){
296                                        _t._startTimer();
297                                }else{
298                                        _t._active = false;
299
300                                        if(_t.repeat > 0){
301                                                _t.repeat--;
302                                                _t.play(null, true);
303                                        }else if(_t.repeat == -1){
304                                                _t.play(null, true);
305                                        }else{
306                                                if(_t._startRepeatCount){
307                                                        _t.repeat = _t._startRepeatCount;
308                                                        _t._startRepeatCount = 0;
309                                                }
310                                        }
311                                        _t._percent = 0;
312                                        _t._fire("onEnd", [_t.node]);
313                                        !_t.repeat && _t._stopTimer();
314                                }
315                        }
316                        return _t; // Animation
317                },
318
319                _clearTimer: function(){
320                        // summary:
321                        //              Clear the play delay timer
322                        clearTimeout(this._delayTimer);
323                        delete this._delayTimer;
324                }
325
326        });
327
328        // the local timer, stubbed into all Animation instances
329        var ctr = 0,
330                timer = null,
331                runner = {
332                        run: function(){}
333                };
334
335        lang.extend(Animation, {
336
337                _startTimer: function(){
338                        if(!this._timer){
339                                this._timer = aspect.after(runner, "run", lang.hitch(this, "_cycle"), true);
340                                ctr++;
341                        }
342                        if(!timer){
343                                timer = setInterval(lang.hitch(runner, "run"), this.rate);
344                        }
345                },
346
347                _stopTimer: function(){
348                        if(this._timer){
349                                this._timer.remove();
350                                this._timer = null;
351                                ctr--;
352                        }
353                        if(ctr <= 0){
354                                clearInterval(timer);
355                                timer = null;
356                                ctr = 0;
357                        }
358                }
359
360        });
361
362        var _makeFadeable =
363                has("ie") ? function(node){
364                        // only set the zoom if the "tickle" value would be the same as the
365                        // default
366                        var ns = node.style;
367                        // don't set the width to auto if it didn't already cascade that way.
368                        // We don't want to f anyones designs
369                        if(!ns.width.length && style.get(node, "width") == "auto"){
370                                ns.width = "auto";
371                        }
372                } :
373                function(){};
374
375        basefx._fade = function(/*Object*/ args){
376                // summary:
377                //              Returns an animation that will fade the node defined by
378                //              args.node from the start to end values passed (args.start
379                //              args.end) (end is mandatory, start is optional)
380
381                args.node = dom.byId(args.node);
382                var fArgs = _mixin({ properties: {} }, args),
383                        props = (fArgs.properties.opacity = {});
384
385                props.start = !("start" in fArgs) ?
386                        function(){
387                                return +style.get(fArgs.node, "opacity")||0;
388                        } : fArgs.start;
389                props.end = fArgs.end;
390
391                var anim = basefx.animateProperty(fArgs);
392                aspect.after(anim, "beforeBegin", lang.partial(_makeFadeable, fArgs.node), true);
393
394                return anim; // Animation
395        };
396
397        /*=====
398        var __FadeArgs = declare(null, {
399                // node: DOMNode|String
400                //              The node referenced in the animation
401                // duration: Integer?
402                //              Duration of the animation in milliseconds.
403                // easing: Function?
404                //              An easing function.
405        });
406        =====*/
407
408        basefx.fadeIn = function(/*__FadeArgs*/ args){
409                // summary:
410                //              Returns an animation that will fade node defined in 'args' from
411                //              its current opacity to fully opaque.
412                return basefx._fade(_mixin({ end: 1 }, args)); // Animation
413        };
414
415        basefx.fadeOut = function(/*__FadeArgs*/ args){
416                // summary:
417                //              Returns an animation that will fade node defined in 'args'
418                //              from its current opacity to fully transparent.
419                return basefx._fade(_mixin({ end: 0 }, args)); // Animation
420        };
421
422        basefx._defaultEasing = function(/*Decimal?*/ n){
423                // summary:
424                //              The default easing function for Animation(s)
425                return 0.5 + ((Math.sin((n + 1.5) * Math.PI)) / 2);     // Decimal
426        };
427
428        var PropLine = function(properties){
429                // PropLine is an internal class which is used to model the values of
430                // an a group of CSS properties across an animation lifecycle. In
431                // particular, the "getValue" function handles getting interpolated
432                // values between start and end for a particular CSS value.
433                this._properties = properties;
434                for(var p in properties){
435                        var prop = properties[p];
436                        if(prop.start instanceof Color){
437                                // create a reusable temp color object to keep intermediate results
438                                prop.tempColor = new Color();
439                        }
440                }
441        };
442
443        PropLine.prototype.getValue = function(r){
444                var ret = {};
445                for(var p in this._properties){
446                        var prop = this._properties[p],
447                                start = prop.start;
448                        if(start instanceof Color){
449                                ret[p] = Color.blendColors(start, prop.end, r, prop.tempColor).toCss();
450                        }else if(!lang.isArray(start)){
451                                ret[p] = ((prop.end - start) * r) + start + (p != "opacity" ? prop.units || "px" : 0);
452                        }
453                }
454                return ret;
455        };
456
457        /*=====
458        var __AnimArgs = declare(__FadeArgs, {
459                // properties: Object?
460                //              A hash map of style properties to Objects describing the transition,
461                //              such as the properties of _Line with an additional 'units' property
462                properties: {}
463
464                //TODOC: add event callbacks
465        });
466        =====*/
467
468        basefx.animateProperty = function(/*__AnimArgs*/ args){
469                // summary:
470                //              Returns an animation that will transition the properties of
471                //              node defined in `args` depending how they are defined in
472                //              `args.properties`
473                //
474                // description:
475                //              Foundation of most `dojo/_base/fx`
476                //              animations. It takes an object of "properties" corresponding to
477                //              style properties, and animates them in parallel over a set
478                //              duration.
479                //
480                // example:
481                //              A simple animation that changes the width of the specified node.
482                //      |       basefx.animateProperty({
483                //      |               node: "nodeId",
484                //      |               properties: { width: 400 },
485                //      |       }).play();
486                //              Dojo figures out the start value for the width and converts the
487                //              integer specified for the width to the more expressive but
488                //              verbose form `{ width: { end: '400', units: 'px' } }` which you
489                //              can also specify directly. Defaults to 'px' if omitted.
490                //
491                // example:
492                //              Animate width, height, and padding over 2 seconds... the
493                //              pedantic way:
494                //      |       basefx.animateProperty({ node: node, duration:2000,
495                //      |               properties: {
496                //      |                       width: { start: '200', end: '400', units:"px" },
497                //      |                       height: { start:'200', end: '400', units:"px" },
498                //      |                       paddingTop: { start:'5', end:'50', units:"px" }
499                //      |               }
500                //      |       }).play();
501                //              Note 'paddingTop' is used over 'padding-top'. Multi-name CSS properties
502                //              are written using "mixed case", as the hyphen is illegal as an object key.
503                //
504                // example:
505                //              Plug in a different easing function and register a callback for
506                //              when the animation ends. Easing functions accept values between
507                //              zero and one and return a value on that basis. In this case, an
508                //              exponential-in curve.
509                //      |       basefx.animateProperty({
510                //      |               node: "nodeId",
511                //      |               // dojo figures out the start value
512                //      |               properties: { width: { end: 400 } },
513                //      |               easing: function(n){
514                //      |                       return (n==0) ? 0 : Math.pow(2, 10 * (n - 1));
515                //      |               },
516                //      |               onEnd: function(node){
517                //      |                       // called when the animation finishes. The animation
518                //      |                       // target is passed to this function
519                //      |               }
520                //      |       }).play(500); // delay playing half a second
521                //
522                // example:
523                //              Like all `Animation`s, animateProperty returns a handle to the
524                //              Animation instance, which fires the events common to Dojo FX. Use `aspect.after`
525                //              to access these events outside of the Animation definition:
526                //      |       var anim = basefx.animateProperty({
527                //      |               node:"someId",
528                //      |               properties:{
529                //      |                       width:400, height:500
530                //      |               }
531                //      |       });
532                //      |       aspect.after(anim, "onEnd", function(){
533                //      |               console.log("animation ended");
534                //      |       }, true);
535                //      |       // play the animation now:
536                //      |       anim.play();
537                //
538                // example:
539                //              Each property can be a function whose return value is substituted along.
540                //              Additionally, each measurement (eg: start, end) can be a function. The node
541                //              reference is passed directly to callbacks.
542                //      |       basefx.animateProperty({
543                //      |               node:"mine",
544                //      |               properties:{
545                //      |                       height:function(node){
546                //      |                               // shrink this node by 50%
547                //      |                               return domGeom.position(node).h / 2
548                //      |                       },
549                //      |                       width:{
550                //      |                               start:function(node){ return 100; },
551                //      |                               end:function(node){ return 200; }
552                //      |                       }
553                //      |               }
554                //      |       }).play();
555                //
556
557                var n = args.node = dom.byId(args.node);
558                if(!args.easing){ args.easing = dojo._defaultEasing; }
559
560                var anim = new Animation(args);
561                aspect.after(anim, "beforeBegin", lang.hitch(anim, function(){
562                        var pm = {};
563                        for(var p in this.properties){
564                                // Make shallow copy of properties into pm because we overwrite
565                                // some values below. In particular if start/end are functions
566                                // we don't want to overwrite them or the functions won't be
567                                // called if the animation is reused.
568                                if(p == "width" || p == "height"){
569                                        this.node.display = "block";
570                                }
571                                var prop = this.properties[p];
572                                if(lang.isFunction(prop)){
573                                        prop = prop(n);
574                                }
575                                prop = pm[p] = _mixin({}, (lang.isObject(prop) ? prop: { end: prop }));
576
577                                if(lang.isFunction(prop.start)){
578                                        prop.start = prop.start(n);
579                                }
580                                if(lang.isFunction(prop.end)){
581                                        prop.end = prop.end(n);
582                                }
583                                var isColor = (p.toLowerCase().indexOf("color") >= 0);
584                                function getStyle(node, p){
585                                        // domStyle.get(node, "height") can return "auto" or "" on IE; this is more reliable:
586                                        var v = { height: node.offsetHeight, width: node.offsetWidth }[p];
587                                        if(v !== undefined){ return v; }
588                                        v = style.get(node, p);
589                                        return (p == "opacity") ? +v : (isColor ? v : parseFloat(v));
590                                }
591                                if(!("end" in prop)){
592                                        prop.end = getStyle(n, p);
593                                }else if(!("start" in prop)){
594                                        prop.start = getStyle(n, p);
595                                }
596
597                                if(isColor){
598                                        prop.start = new Color(prop.start);
599                                        prop.end = new Color(prop.end);
600                                }else{
601                                        prop.start = (p == "opacity") ? +prop.start : parseFloat(prop.start);
602                                }
603                        }
604                        this.curve = new PropLine(pm);
605                }), true);
606                aspect.after(anim, "onAnimate", lang.hitch(style, "set", anim.node), true);
607                return anim; // Animation
608        };
609
610        basefx.anim = function( /*DOMNode|String*/      node,
611                                                        /*Object*/                      properties,
612                                                        /*Integer?*/            duration,
613                                                        /*Function?*/           easing,
614                                                        /*Function?*/           onEnd,
615                                                        /*Integer?*/            delay){
616                // summary:
617                //              A simpler interface to `animateProperty()`, also returns
618                //              an instance of `Animation` but begins the animation
619                //              immediately, unlike nearly every other Dojo animation API.
620                // description:
621                //              Simpler (but somewhat less powerful) version
622                //              of `animateProperty`.  It uses defaults for many basic properties
623                //              and allows for positional parameters to be used in place of the
624                //              packed "property bag" which is used for other Dojo animation
625                //              methods.
626                //
627                //              The `Animation` object returned will be already playing, so
628                //              calling play() on it again is (usually) a no-op.
629                // node:
630                //              a DOM node or the id of a node to animate CSS properties on
631                // duration:
632                //              The number of milliseconds over which the animation
633                //              should run. Defaults to the global animation default duration
634                //              (350ms).
635                // easing:
636                //              An easing function over which to calculate acceleration
637                //              and deceleration of the animation through its duration.
638                //              A default easing algorithm is provided, but you may
639                //              plug in any you wish. A large selection of easing algorithms
640                //              are available in `dojo/fx/easing`.
641                // onEnd:
642                //              A function to be called when the animation finishes
643                //              running.
644                // delay:
645                //              The number of milliseconds to delay beginning the
646                //              animation by. The default is 0.
647                // example:
648                //              Fade out a node
649                //      |       basefx.anim("id", { opacity: 0 });
650                // example:
651                //              Fade out a node over a full second
652                //      |       basefx.anim("id", { opacity: 0 }, 1000);
653                return basefx.animateProperty({ // Animation
654                        node: node,
655                        duration: duration || Animation.prototype.duration,
656                        properties: properties,
657                        easing: easing,
658                        onEnd: onEnd
659                }).play(delay || 0);
660        };
661
662
663        if(has("extend-dojo")){
664                _mixin(dojo, basefx);
665                // Alias to drop come 2.0:
666                dojo._Animation = Animation;
667        }
668
669        return basefx;
670});
Note: See TracBrowser for help on using the repository browser.