1 if(!dojo._hasResource["dojo._base.fx"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dojo._base.fx"] = true;
3 dojo.provide("dojo._base.fx");
4 dojo.require("dojo._base.Color");
5 dojo.require("dojo._base.connect");
6 dojo.require("dojo._base.declare");
7 dojo.require("dojo._base.lang");
8 dojo.require("dojo._base.html");
11 Animation losely package based on Dan Pupius' work, contributed under CLA:
12 http://pupius.co.uk/js/Toolkit.Drawing.js
18 dojo._Line = function(/*int*/ start, /*int*/ end){
20 // dojo._Line is the object used to generate values from a start value
23 // Beginning value for range
25 // Ending value for range
28 this.getValue = function(/*float*/ n){
29 // summary: returns the point on the line
30 // n: a floating point number greater than 0 and less than 1
31 return ((this.end - this.start) * n) + this.start; // Decimal
35 d.declare("dojo._Animation", null, {
37 // A generic animation class that fires callbacks into its handlers
38 // object at various states. Nearly all dojo animation functions
39 // return an instance of this method, usually without calling the
40 // .play() method beforehand. Therefore, you will likely need to
41 // call .play() on instances of dojo._Animation when one is
43 constructor: function(/*Object*/ args){
45 if(d.isArray(this.curve)){
48 this.curve = new d._Line(this.curve[0], this.curve[1]);
53 // The time in milliseonds the animation will take to run
57 // curve: dojo._Line||Array
58 // A two element array of start and end values, or a dojo._Line instance to be
59 // used in the Animation.
63 // A Function to adjust the acceleration (or deceleration) of the progress
64 // across a dojo._Line
69 // The number of times to loop the animation
73 // the time in milliseconds to wait before advancing to next frame
74 // (used as a fps timer: rate/1000 = fps)
75 rate: 10 /* 100 fps */,
79 // The time in milliseconds to wait before starting animation after it has been .play()'ed
85 // Synthetic event fired before a dojo._Animation begins playing (synchronous)
89 // Synthetic event fired as a dojo._Animation begins playing (useful?)
93 // Synthetic event fired at each interval of a dojo._Animation
97 // Synthetic event fired after the final frame of a dojo._Animation
101 // Synthetic event fired any time a dojo._Animation is play()'ed
105 // Synthetic event fired when a dojo._Animation is paused
109 // Synthetic event fires when a dojo._Animation is stopped
115 _startRepeatCount: 0,
117 _fire: function(/*Event*/ evt, /*Array?*/ args){
119 // Convenience function. Fire event "evt" and pass it the
120 // arguments specified in "args".
122 // The event to fire.
124 // The arguments to pass to the event.
127 this[evt].apply(this, args||[]);
130 // squelch and log because we shouldn't allow exceptions in
131 // synthetic event handlers to cause the internal timer to run
132 // amuck, potentially pegging the CPU. I'm not a fan of this
133 // squelch, but hopefully logging will make it clear what's
135 console.error("exception in animation handler for:", evt);
138 return this; // dojo._Animation
141 play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){
143 // Start the animation.
145 // How many milliseconds to delay before starting.
147 // If true, starts the animation from the beginning; otherwise,
148 // starts it from its current position.
152 _t._active = _t._paused = false;
154 }else if(_t._active && !_t._paused){
155 return _t; // dojo._Animation
158 _t._fire("beforeBegin");
160 var de = delay||_t.delay;
161 var _p = dojo.hitch(_t, "_play", gotoStart);
164 return _t; // dojo._Animation
170 _play: function(gotoStart){
172 _t._startTime = new Date().valueOf();
174 _t._startTime -= _t.duration * _t._percent;
176 _t._endTime = _t._startTime + _t.duration;
181 var value = _t.curve.getValue(_t._percent);
183 if(!_t._startRepeatCount){
184 _t._startRepeatCount = _t.repeat;
186 _t._fire("onBegin", [value]);
189 _t._fire("onPlay", [value]);
192 return _t; // dojo._Animation
196 // summary: Pauses a running animation.
198 if(!this._active){ return this; /*dojo._Animation*/ }
200 this._fire("onPause", [this.curve.getValue(this._percent)]);
201 return this; // dojo._Animation
204 gotoPercent: function(/*Decimal*/ percent, /*Boolean?*/ andPlay){
206 // Sets the progress of the animation.
208 // A percentage in decimal notation (between and including 0.0 and 1.0).
210 // If true, play the animation after setting the progress.
212 this._active = this._paused = true;
213 this._percent = percent;
214 if(andPlay){ this.play(); }
215 return this; // dojo._Animation
218 stop: function(/*boolean?*/ gotoEnd){
219 // summary: Stops a running animation.
220 // gotoEnd: If true, the animation will end.
221 if(!this._timer){ return this; /* dojo._Animation */ }
226 this._fire("onStop", [this.curve.getValue(this._percent)]);
227 this._active = this._paused = false;
228 return this; // dojo._Animation
232 // summary: Returns a string token representation of the status of
233 // the animation, one of: "paused", "playing", "stopped"
235 return this._paused ? "paused" : "playing"; // String
237 return "stopped"; // String
243 var curr = new Date().valueOf();
244 var step = (curr - _t._startTime) / (_t._endTime - _t._startTime);
253 step = _t.easing(step);
256 _t._fire("onAnimate", [_t.curve.getValue(step)]);
266 }else if(_t.repeat == -1){
269 if(_t._startRepeatCount){
270 _t.repeat = _t._startRepeatCount;
271 _t._startRepeatCount = 0;
279 return _t; // dojo._Animation
284 var _globalTimerList = [];
289 dojo._Animation.prototype._startTimer = function(){
290 // this._timer = setTimeout(dojo.hitch(this, "_cycle"), this.rate);
292 this._timer = d.connect(runner, "run", this, "_cycle");
296 timer = setInterval(d.hitch(runner, "run"), this.rate);
300 dojo._Animation.prototype._stopTimer = function(){
302 d.disconnect(this._timer);
307 clearInterval(timer);
313 var _makeFadeable = (d.isIE) ? function(node){
314 // only set the zoom if the "tickle" value would be the same as the
317 if(!ns.zoom.length && d.style(node, "zoom") == "normal"){
318 // make sure the node "hasLayout"
319 // NOTE: this has been tested with larger and smaller user-set text
320 // sizes and works fine
322 // node.style.zoom = "normal";
324 // don't set the width to auto if it didn't already cascade that way.
325 // We don't want to f anyones designs
326 if(!ns.width.length && d.style(node, "width") == "auto"){
331 dojo._fade = function(/*Object*/ args){
333 // Returns an animation that will fade the node defined by
334 // args.node from the start to end values passed (args.start
335 // args.end) (end is mandatory, start is optional)
337 args.node = d.byId(args.node);
338 var fArgs = d.mixin({ properties: {} }, args);
339 var props = (fArgs.properties.opacity = {});
340 props.start = !("start" in fArgs) ?
342 return Number(d.style(fArgs.node, "opacity"));
344 props.end = fArgs.end;
346 var anim = d.animateProperty(fArgs);
347 d.connect(anim, "beforeBegin", d.partial(_makeFadeable, fArgs.node));
349 return anim; // dojo._Animation
353 dojo.__FadeArgs = function(node, duration, easing){
354 // node: DOMNode|String
355 // The node referenced in the animation
356 // duration: Integer?
357 // Duration of the animation in milliseconds.
359 // An easing function.
361 this.duration = duration;
362 this.easing = easing;
366 dojo.fadeIn = function(/*dojo.__FadeArgs*/ args){
368 // Returns an animation that will fade node defined in 'args' from
369 // its current opacity to fully opaque.
370 return d._fade(d.mixin({ end: 1 }, args)); // dojo._Animation
373 dojo.fadeOut = function(/*dojo.__FadeArgs*/ args){
375 // Returns an animation that will fade node defined in 'args'
376 // from its current opacity to fully transparent.
377 return d._fade(d.mixin({ end: 0 }, args)); // dojo._Animation
380 dojo._defaultEasing = function(/*Decimal?*/ n){
381 // summary: The default easing function for dojo._Animation(s)
382 return 0.5 + ((Math.sin((n + 1.5) * Math.PI))/2);
385 var PropLine = function(properties){
386 // PropLine is an internal class which is used to model the values of
387 // an a group of CSS properties across an animation lifecycle. In
388 // particular, the "getValue" function handles getting interpolated
389 // values between start and end for a particular CSS value.
390 this._properties = properties;
391 for(var p in properties){
392 var prop = properties[p];
393 if(prop.start instanceof d.Color){
394 // create a reusable temp color object to keep intermediate results
395 prop.tempColor = new d.Color();
398 this.getValue = function(r){
400 for(var p in this._properties){
401 var prop = this._properties[p];
402 var start = prop.start;
403 if(start instanceof d.Color){
404 ret[p] = d.blendColors(start, prop.end, r, prop.tempColor).toCss();
405 }else if(!d.isArray(start)){
406 ret[p] = ((prop.end - start) * r) + start + (p != "opacity" ? prop.units||"px" : "");
414 dojo.declare("dojo.__AnimArgs", [dojo.__FadeArgs], {
415 // Properties: Object?
416 // A hash map of style properties to Objects describing the transition,
417 // such as the properties of dojo._Line with an additional 'unit' property
420 //TODOC: add event callbacks
424 dojo.animateProperty = function(/*dojo.__AnimArgs*/ args){
426 // Returns an animation that will transition the properties of
427 // node defined in 'args' depending how they are defined in
431 // dojo.animateProperty is the foundation of most dojo.fx
432 // animations. It takes an object of "properties" corresponding to
433 // style properties, and animates them in parallel over a set
437 // A simple animation that changes the width of the specified node.
438 // | dojo.animateProperty({
440 // | properties: { width: 400 },
442 // Dojo figures out the start value for the width and converts the
443 // integer specified for the width to the more expressive but
444 // verbose form `{ width: { end: '400', units: 'px' } }` which you
445 // can also specify directly
447 // animate width, height, and padding over 2 seconds...the
449 // | dojo.animateProperty({ node: node, duration:2000,
451 // | width: { start: '200', end: '400', unit:"px" },
452 // | height: { start:'200', end: '400', unit:"px" },
453 // | paddingTop: { start:'5', end:'50', unit:"px" }
458 // plug in a different easing function and register a callback for
459 // when the animation ends. Easing functions accept values between
460 // zero and one and return a value on that basis. In this case, an
461 // exponential-in curve.
462 // | dojo.animateProperty({
464 // | // dojo figures out the start value
465 // | properties: { width: { end: 400 } },
466 // | easing: function(n){
467 // | return (n==0) ? 0 : Math.pow(2, 10 * (n - 1));
469 // | onEnd: function(){
470 // | // called when the animation finishes
472 // | }).play(500); // delay playing half a second
474 args.node = d.byId(args.node);
475 if(!args.easing){ args.easing = d._defaultEasing; }
477 var anim = new d._Animation(args);
478 d.connect(anim, "beforeBegin", anim, function(){
480 for(var p in this.properties){
481 // Make shallow copy of properties into pm because we overwrite
482 // some values below. In particular if start/end are functions
483 // we don't want to overwrite them or the functions won't be
484 // called if the animation is reused.
485 if(p == "width" || p == "height"){
486 this.node.display = "block";
488 var prop = this.properties[p];
489 prop = pm[p] = d.mixin({}, (d.isObject(prop) ? prop: { end: prop }));
491 if(d.isFunction(prop.start)){
492 prop.start = prop.start();
494 if(d.isFunction(prop.end)){
495 prop.end = prop.end();
497 var isColor = (p.toLowerCase().indexOf("color") >= 0);
498 function getStyle(node, p){
499 // dojo.style(node, "height") can return "auto" or "" on IE; this is more reliable:
500 var v = ({height: node.offsetHeight, width: node.offsetWidth})[p];
501 if(v !== undefined){ return v; }
502 v = d.style(node, p);
503 return (p=="opacity") ? Number(v) : (isColor ? v : parseFloat(v));
505 if(!("end" in prop)){
506 prop.end = getStyle(this.node, p);
507 }else if(!("start" in prop)){
508 prop.start = getStyle(this.node, p);
512 prop.start = new d.Color(prop.start);
513 prop.end = new d.Color(prop.end);
515 prop.start = (p == "opacity") ? Number(prop.start) : parseFloat(prop.start);
518 this.curve = new PropLine(pm);
520 d.connect(anim, "onAnimate", anim, function(propValues){
522 for(var s in propValues){
523 d.style(this.node, s, propValues[s]);
524 // this.node.style[s] = propValues[s];
527 return anim; // dojo._Animation
530 dojo.anim = function( /*DOMNode|String*/ node,
531 /*Object*/ properties,
532 /*Integer?*/ duration,
533 /*Function?*/ easing,
537 // A simpler interface to `dojo.animateProperty()`, also returns
538 // an instance of `dojo._Animation` but begins the animation
539 // immediately, unlike nearly every other Dojo animation API.
541 // `dojo.anim` is a simpler (but somewhat less powerful) version
542 // of `dojo.animateProperty`. It uses defaults for many basic properties
543 // and allows for positional parameters to be used in place of the
544 // packed "property bag" which is used for other Dojo animation
547 // The `dojo._Animation` object returned from `dojo.anim` will be
548 // already playing when it is returned from this function, so
549 // calling play() on it again is (usually) a no-op.
551 // a DOM node or the id of a node to animate CSS properties on
553 // The number of milliseconds over which the animation
554 // should run. Defaults to the global animation default duration
557 // An easing function over which to calculate acceleration
558 // and deceleration of the animation through its duration.
559 // A default easing algorithm is provided, but you may
560 // plug in any you wish. A large selection of easing algorithms
561 // are available in `dojox.fx.easing`.
563 // A function to be called when the animation finishes
566 // The number of milliseconds to delay beginning the
567 // animation by. The default is 0.
570 // | dojo.anim("id", { opacity: 0 });
572 // Fade out a node over a full second
573 // | dojo.anim("id", { opacity: 0 }, 1000);
574 return d.animateProperty({
576 duration: duration||d._Animation.prototype.duration,
577 properties: properties,