1 define("dojo/_base/fx", ["./kernel", "./lang", "../Evented", "./Color", "./connect", "./sniff", "../dom", "../dom-style"], function(dojo, lang, Evented, Color, connect, has, dom, style){
5 // This module defines the base dojo.fx implementation.
7 // Animation loosely package based on Dan Pupius' work, contributed under CLA; see
8 // http://pupius.co.uk/js/Toolkit.Drawing.js
10 var _mixin = lang.mixin;
12 dojo._Line = function(/*int*/ start, /*int*/ end){
14 // dojo._Line is the object used to generate values from a start value
17 // Beginning value for range
19 // Ending value for range
24 dojo._Line.prototype.getValue = function(/*float*/ n){
25 // summary: Returns the point on the line
26 // n: a floating point number greater than 0 and less than 1
27 return ((this.end - this.start) * n) + this.start; // Decimal
30 dojo.Animation = function(args){
32 // A generic animation class that fires callbacks into its handlers
33 // object at various states.
35 // A generic animation class that fires callbacks into its handlers
36 // object at various states. Nearly all dojo animation functions
37 // return an instance of this method, usually without calling the
38 // .play() method beforehand. Therefore, you will likely need to
39 // call .play() on instances of `dojo.Animation` when one is
42 // The 'magic argument', mixing all the properties into this
43 // animation instance.
46 if(lang.isArray(this.curve)){
47 this.curve = new dojo._Line(this.curve[0], this.curve[1]);
51 dojo.Animation.prototype = new Evented();
52 // Alias to drop come 2.0:
53 dojo._Animation = dojo.Animation;
55 lang.extend(dojo.Animation, {
57 // The time in milliseonds the animation will take to run
61 // curve: dojo._Line|Array
62 // A two element array of start and end values, or a `dojo._Line` instance to be
63 // used in the Animation.
67 // A Function to adjust the acceleration (or deceleration) of the progress
68 // across a dojo._Line
73 // The number of times to loop the animation
77 // the time in milliseconds to wait before advancing to next frame
78 // (used as a fps timer: 1000/rate = fps)
79 rate: 20 /* 50 fps */,
83 // The time in milliseconds to wait before starting animation after it
84 // has been .play()'ed
87 // beforeBegin: Event?
88 // Synthetic event fired before a dojo.Animation begins playing (synchronous)
92 // Synthetic event fired as a dojo.Animation begins playing (useful?)
96 // Synthetic event fired at each interval of a `dojo.Animation`
100 // Synthetic event fired after the final frame of a `dojo.Animation`
104 // Synthetic event fired any time a `dojo.Animation` is play()'ed
108 // Synthetic event fired when a `dojo.Animation` is paused
112 // Synthetic event fires when a `dojo.Animation` is stopped
118 _startRepeatCount: 0,
120 _getStep: function(){
121 var _p = this._percent,
124 return _e ? _e(_p) : _p;
126 _fire: function(/*Event*/ evt, /*Array?*/ args){
128 // Convenience function. Fire event "evt" and pass it the
129 // arguments specified in "args".
131 // Convenience function. Fire event "evt" and pass it the
132 // arguments specified in "args".
133 // Fires the callback in the scope of the `dojo.Animation`
136 // The event to fire.
138 // The arguments to pass to the event.
141 if(dojo.config.debugAtAllCosts){
142 this[evt].apply(this, a);
145 this[evt].apply(this, a);
147 // squelch and log because we shouldn't allow exceptions in
148 // synthetic event handlers to cause the internal timer to run
149 // amuck, potentially pegging the CPU. I'm not a fan of this
150 // squelch, but hopefully logging will make it clear what's
152 console.error("exception in animation handler for:", evt);
157 return this; // dojo.Animation
160 play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){
162 // Start the animation.
164 // How many milliseconds to delay before starting.
166 // If true, starts the animation from the beginning; otherwise,
167 // starts it from its current position.
168 // returns: dojo.Animation
169 // The instance to allow chaining.
172 if(_t._delayTimer){ _t._clearTimer(); }
175 _t._active = _t._paused = false;
177 }else if(_t._active && !_t._paused){
181 _t._fire("beforeBegin", [_t.node]);
183 var de = delay || _t.delay,
184 _p = lang.hitch(_t, "_play", gotoStart);
187 _t._delayTimer = setTimeout(_p, de);
191 return _t; // dojo.Animation
194 _play: function(gotoStart){
196 if(_t._delayTimer){ _t._clearTimer(); }
197 _t._startTime = new Date().valueOf();
199 _t._startTime -= _t.duration * _t._percent;
204 var value = _t.curve.getValue(_t._getStep());
206 if(!_t._startRepeatCount){
207 _t._startRepeatCount = _t.repeat;
209 _t._fire("onBegin", [value]);
212 _t._fire("onPlay", [value]);
215 return _t; // dojo.Animation
219 // summary: Pauses a running animation.
221 if(_t._delayTimer){ _t._clearTimer(); }
223 if(!_t._active){ return _t; /*dojo.Animation*/ }
225 _t._fire("onPause", [_t.curve.getValue(_t._getStep())]);
226 return _t; // dojo.Animation
229 gotoPercent: function(/*Decimal*/ percent, /*Boolean?*/ andPlay){
231 // Sets the progress of the animation.
233 // A percentage in decimal notation (between and including 0.0 and 1.0).
235 // If true, play the animation after setting the progress.
238 _t._active = _t._paused = true;
239 _t._percent = percent;
240 if(andPlay){ _t.play(); }
241 return _t; // dojo.Animation
244 stop: function(/*boolean?*/ gotoEnd){
245 // summary: Stops a running animation.
246 // gotoEnd: If true, the animation will end.
248 if(_t._delayTimer){ _t._clearTimer(); }
249 if(!_t._timer){ return _t; /* dojo.Animation */ }
254 _t._fire("onStop", [_t.curve.getValue(_t._getStep())]);
255 _t._active = _t._paused = false;
256 return _t; // dojo.Animation
261 // Returns a string token representation of the status of
262 // the animation, one of: "paused", "playing", "stopped"
264 return this._paused ? "paused" : "playing"; // String
266 return "stopped"; // String
272 var curr = new Date().valueOf();
273 var step = (curr - _t._startTime) / (_t.duration);
282 step = _t.easing(step);
285 _t._fire("onAnimate", [_t.curve.getValue(step)]);
295 }else if(_t.repeat == -1){
298 if(_t._startRepeatCount){
299 _t.repeat = _t._startRepeatCount;
300 _t._startRepeatCount = 0;
304 _t._fire("onEnd", [_t.node]);
305 !_t.repeat && _t._stopTimer();
308 return _t; // dojo.Animation
311 _clearTimer: function(){
312 // summary: Clear the play delay timer
313 clearTimeout(this._delayTimer);
314 delete this._delayTimer;
319 // the local timer, stubbed into all Animation instances
326 lang.extend(dojo.Animation, {
328 _startTimer: function(){
330 this._timer = connect.connect(runner, "run", this, "_cycle");
334 timer = setInterval(lang.hitch(runner, "run"), this.rate);
338 _stopTimer: function(){
340 connect.disconnect(this._timer);
345 clearInterval(timer);
354 has("ie") ? function(node){
355 // only set the zoom if the "tickle" value would be the same as the
358 // don't set the width to auto if it didn't already cascade that way.
359 // We don't want to f anyones designs
360 if(!ns.width.length && style.get(node, "width") == "auto"){
366 dojo._fade = function(/*Object*/ args){
368 // Returns an animation that will fade the node defined by
369 // args.node from the start to end values passed (args.start
370 // args.end) (end is mandatory, start is optional)
372 args.node = dom.byId(args.node);
373 var fArgs = _mixin({ properties: {} }, args),
374 props = (fArgs.properties.opacity = {});
376 props.start = !("start" in fArgs) ?
378 return +style.get(fArgs.node, "opacity")||0;
380 props.end = fArgs.end;
382 var anim = dojo.animateProperty(fArgs);
383 connect.connect(anim, "beforeBegin", lang.partial(_makeFadeable, fArgs.node));
385 return anim; // dojo.Animation
389 dojo.__FadeArgs = function(node, duration, easing){
390 // node: DOMNode|String
391 // The node referenced in the animation
392 // duration: Integer?
393 // Duration of the animation in milliseconds.
395 // An easing function.
397 this.duration = duration;
398 this.easing = easing;
402 dojo.fadeIn = function(/*dojo.__FadeArgs*/ args){
404 // Returns an animation that will fade node defined in 'args' from
405 // its current opacity to fully opaque.
406 return dojo._fade(_mixin({ end: 1 }, args)); // dojo.Animation
409 dojo.fadeOut = function(/*dojo.__FadeArgs*/ args){
411 // Returns an animation that will fade node defined in 'args'
412 // from its current opacity to fully transparent.
413 return dojo._fade(_mixin({ end: 0 }, args)); // dojo.Animation
416 dojo._defaultEasing = function(/*Decimal?*/ n){
417 // summary: The default easing function for dojo.Animation(s)
418 return 0.5 + ((Math.sin((n + 1.5) * Math.PI)) / 2); // Decimal
421 var PropLine = function(properties){
422 // PropLine is an internal class which is used to model the values of
423 // an a group of CSS properties across an animation lifecycle. In
424 // particular, the "getValue" function handles getting interpolated
425 // values between start and end for a particular CSS value.
426 this._properties = properties;
427 for(var p in properties){
428 var prop = properties[p];
429 if(prop.start instanceof Color){
430 // create a reusable temp color object to keep intermediate results
431 prop.tempColor = new Color();
436 PropLine.prototype.getValue = function(r){
438 for(var p in this._properties){
439 var prop = this._properties[p],
441 if(start instanceof Color){
442 ret[p] = Color.blendColors(start, prop.end, r, prop.tempColor).toCss();
443 }else if(!lang.isArray(start)){
444 ret[p] = ((prop.end - start) * r) + start + (p != "opacity" ? prop.units || "px" : 0);
451 dojo.declare("dojo.__AnimArgs", [dojo.__FadeArgs], {
452 // Properties: Object?
453 // A hash map of style properties to Objects describing the transition,
454 // such as the properties of dojo._Line with an additional 'units' property
457 //TODOC: add event callbacks
461 dojo.animateProperty = function(/*dojo.__AnimArgs*/ args){
463 // Returns an animation that will transition the properties of
464 // node defined in `args` depending how they are defined in
468 // `dojo.animateProperty` is the foundation of most `dojo.fx`
469 // animations. It takes an object of "properties" corresponding to
470 // style properties, and animates them in parallel over a set
474 // A simple animation that changes the width of the specified node.
475 // | dojo.animateProperty({
477 // | properties: { width: 400 },
479 // Dojo figures out the start value for the width and converts the
480 // integer specified for the width to the more expressive but
481 // verbose form `{ width: { end: '400', units: 'px' } }` which you
482 // can also specify directly. Defaults to 'px' if ommitted.
485 // Animate width, height, and padding over 2 seconds... the
487 // | dojo.animateProperty({ node: node, duration:2000,
489 // | width: { start: '200', end: '400', units:"px" },
490 // | height: { start:'200', end: '400', units:"px" },
491 // | paddingTop: { start:'5', end:'50', units:"px" }
494 // Note 'paddingTop' is used over 'padding-top'. Multi-name CSS properties
495 // are written using "mixed case", as the hyphen is illegal as an object key.
498 // Plug in a different easing function and register a callback for
499 // when the animation ends. Easing functions accept values between
500 // zero and one and return a value on that basis. In this case, an
501 // exponential-in curve.
502 // | dojo.animateProperty({
504 // | // dojo figures out the start value
505 // | properties: { width: { end: 400 } },
506 // | easing: function(n){
507 // | return (n==0) ? 0 : Math.pow(2, 10 * (n - 1));
509 // | onEnd: function(node){
510 // | // called when the animation finishes. The animation
511 // | // target is passed to this function
513 // | }).play(500); // delay playing half a second
516 // Like all `dojo.Animation`s, animateProperty returns a handle to the
517 // Animation instance, which fires the events common to Dojo FX. Use `dojo.connect`
518 // to access these events outside of the Animation definiton:
519 // | var anim = dojo.animateProperty({
522 // | width:400, height:500
525 // | dojo.connect(anim,"onEnd", function(){
526 // | console.log("animation ended");
528 // | // play the animation now:
532 // Each property can be a function whose return value is substituted along.
533 // Additionally, each measurement (eg: start, end) can be a function. The node
534 // reference is passed direcly to callbacks.
535 // | dojo.animateProperty({
538 // | height:function(node){
539 // | // shrink this node by 50%
540 // | return dojo.position(node).h / 2
543 // | start:function(node){ return 100; },
544 // | end:function(node){ return 200; }
550 var n = args.node = dom.byId(args.node);
551 if(!args.easing){ args.easing = dojo._defaultEasing; }
553 var anim = new dojo.Animation(args);
554 connect.connect(anim, "beforeBegin", anim, function(){
556 for(var p in this.properties){
557 // Make shallow copy of properties into pm because we overwrite
558 // some values below. In particular if start/end are functions
559 // we don't want to overwrite them or the functions won't be
560 // called if the animation is reused.
561 if(p == "width" || p == "height"){
562 this.node.display = "block";
564 var prop = this.properties[p];
565 if(lang.isFunction(prop)){
568 prop = pm[p] = _mixin({}, (lang.isObject(prop) ? prop: { end: prop }));
570 if(lang.isFunction(prop.start)){
571 prop.start = prop.start(n);
573 if(lang.isFunction(prop.end)){
574 prop.end = prop.end(n);
576 var isColor = (p.toLowerCase().indexOf("color") >= 0);
577 function getStyle(node, p){
578 // dojo.style(node, "height") can return "auto" or "" on IE; this is more reliable:
579 var v = { height: node.offsetHeight, width: node.offsetWidth }[p];
580 if(v !== undefined){ return v; }
581 v = style.get(node, p);
582 return (p == "opacity") ? +v : (isColor ? v : parseFloat(v));
584 if(!("end" in prop)){
585 prop.end = getStyle(n, p);
586 }else if(!("start" in prop)){
587 prop.start = getStyle(n, p);
591 prop.start = new Color(prop.start);
592 prop.end = new Color(prop.end);
594 prop.start = (p == "opacity") ? +prop.start : parseFloat(prop.start);
597 this.curve = new PropLine(pm);
599 connect.connect(anim, "onAnimate", lang.hitch(style, "set", anim.node));
600 return anim; // dojo.Animation
603 dojo.anim = function( /*DOMNode|String*/ node,
604 /*Object*/ properties,
605 /*Integer?*/ duration,
606 /*Function?*/ easing,
610 // A simpler interface to `dojo.animateProperty()`, also returns
611 // an instance of `dojo.Animation` but begins the animation
612 // immediately, unlike nearly every other Dojo animation API.
614 // `dojo.anim` is a simpler (but somewhat less powerful) version
615 // of `dojo.animateProperty`. It uses defaults for many basic properties
616 // and allows for positional parameters to be used in place of the
617 // packed "property bag" which is used for other Dojo animation
620 // The `dojo.Animation` object returned from `dojo.anim` will be
621 // already playing when it is returned from this function, so
622 // calling play() on it again is (usually) a no-op.
624 // a DOM node or the id of a node to animate CSS properties on
626 // The number of milliseconds over which the animation
627 // should run. Defaults to the global animation default duration
630 // An easing function over which to calculate acceleration
631 // and deceleration of the animation through its duration.
632 // A default easing algorithm is provided, but you may
633 // plug in any you wish. A large selection of easing algorithms
634 // are available in `dojo.fx.easing`.
636 // A function to be called when the animation finishes
639 // The number of milliseconds to delay beginning the
640 // animation by. The default is 0.
643 // | dojo.anim("id", { opacity: 0 });
645 // Fade out a node over a full second
646 // | dojo.anim("id", { opacity: 0 }, 1000);
647 return dojo.animateProperty({ // dojo.Animation
649 duration: duration || dojo.Animation.prototype.duration,
650 properties: properties,
658 Animation: dojo.Animation,
661 fadeOut: dojo.fadeOut,
662 _defaultEasing: dojo._defaultEasing,
663 animateProperty: dojo.animateProperty,