//>>built define("dojo/_base/fx", ["./kernel", "./lang", "../Evented", "./Color", "./connect", "./sniff", "../dom", "../dom-style"], function(dojo, lang, Evented, Color, connect, has, dom, style){ // module: // dojo/_base/fx // summary: // This module defines the base dojo.fx implementation. // notes: // Animation loosely package based on Dan Pupius' work, contributed under CLA; see // http://pupius.co.uk/js/Toolkit.Drawing.js var _mixin = lang.mixin; dojo._Line = function(/*int*/ start, /*int*/ end){ // summary: // dojo._Line is the object used to generate values from a start value // to an end value // start: int // Beginning value for range // end: int // Ending value for range this.start = start; this.end = end; }; dojo._Line.prototype.getValue = function(/*float*/ n){ // summary: Returns the point on the line // n: a floating point number greater than 0 and less than 1 return ((this.end - this.start) * n) + this.start; // Decimal }; dojo.Animation = function(args){ // summary: // A generic animation class that fires callbacks into its handlers // object at various states. // description: // A generic animation class that fires callbacks into its handlers // object at various states. Nearly all dojo animation functions // return an instance of this method, usually without calling the // .play() method beforehand. Therefore, you will likely need to // call .play() on instances of `dojo.Animation` when one is // returned. // args: Object // The 'magic argument', mixing all the properties into this // animation instance. _mixin(this, args); if(lang.isArray(this.curve)){ this.curve = new dojo._Line(this.curve[0], this.curve[1]); } }; dojo.Animation.prototype = new Evented(); // Alias to drop come 2.0: dojo._Animation = dojo.Animation; lang.extend(dojo.Animation, { // duration: Integer // The time in milliseonds the animation will take to run duration: 350, /*===== // curve: dojo._Line|Array // A two element array of start and end values, or a `dojo._Line` instance to be // used in the Animation. curve: null, // easing: Function? // A Function to adjust the acceleration (or deceleration) of the progress // across a dojo._Line easing: null, =====*/ // repeat: Integer? // The number of times to loop the animation repeat: 0, // rate: Integer? // the time in milliseconds to wait before advancing to next frame // (used as a fps timer: 1000/rate = fps) rate: 20 /* 50 fps */, /*===== // delay: Integer? // The time in milliseconds to wait before starting animation after it // has been .play()'ed delay: null, // beforeBegin: Event? // Synthetic event fired before a dojo.Animation begins playing (synchronous) beforeBegin: null, // onBegin: Event? // Synthetic event fired as a dojo.Animation begins playing (useful?) onBegin: null, // onAnimate: Event? // Synthetic event fired at each interval of a `dojo.Animation` onAnimate: null, // onEnd: Event? // Synthetic event fired after the final frame of a `dojo.Animation` onEnd: null, // onPlay: Event? // Synthetic event fired any time a `dojo.Animation` is play()'ed onPlay: null, // onPause: Event? // Synthetic event fired when a `dojo.Animation` is paused onPause: null, // onStop: Event // Synthetic event fires when a `dojo.Animation` is stopped onStop: null, =====*/ _percent: 0, _startRepeatCount: 0, _getStep: function(){ var _p = this._percent, _e = this.easing ; return _e ? _e(_p) : _p; }, _fire: function(/*Event*/ evt, /*Array?*/ args){ // summary: // Convenience function. Fire event "evt" and pass it the // arguments specified in "args". // description: // Convenience function. Fire event "evt" and pass it the // arguments specified in "args". // Fires the callback in the scope of the `dojo.Animation` // instance. // evt: // The event to fire. // args: // The arguments to pass to the event. var a = args||[]; if(this[evt]){ if(dojo.config.debugAtAllCosts){ this[evt].apply(this, a); }else{ try{ this[evt].apply(this, a); }catch(e){ // squelch and log because we shouldn't allow exceptions in // synthetic event handlers to cause the internal timer to run // amuck, potentially pegging the CPU. I'm not a fan of this // squelch, but hopefully logging will make it clear what's // going on console.error("exception in animation handler for:", evt); console.error(e); } } } return this; // dojo.Animation }, play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){ // summary: // Start the animation. // delay: // How many milliseconds to delay before starting. // gotoStart: // If true, starts the animation from the beginning; otherwise, // starts it from its current position. // returns: dojo.Animation // The instance to allow chaining. var _t = this; if(_t._delayTimer){ _t._clearTimer(); } if(gotoStart){ _t._stopTimer(); _t._active = _t._paused = false; _t._percent = 0; }else if(_t._active && !_t._paused){ return _t; } _t._fire("beforeBegin", [_t.node]); var de = delay || _t.delay, _p = lang.hitch(_t, "_play", gotoStart); if(de > 0){ _t._delayTimer = setTimeout(_p, de); return _t; } _p(); return _t; // dojo.Animation }, _play: function(gotoStart){ var _t = this; if(_t._delayTimer){ _t._clearTimer(); } _t._startTime = new Date().valueOf(); if(_t._paused){ _t._startTime -= _t.duration * _t._percent; } _t._active = true; _t._paused = false; var value = _t.curve.getValue(_t._getStep()); if(!_t._percent){ if(!_t._startRepeatCount){ _t._startRepeatCount = _t.repeat; } _t._fire("onBegin", [value]); } _t._fire("onPlay", [value]); _t._cycle(); return _t; // dojo.Animation }, pause: function(){ // summary: Pauses a running animation. var _t = this; if(_t._delayTimer){ _t._clearTimer(); } _t._stopTimer(); if(!_t._active){ return _t; /*dojo.Animation*/ } _t._paused = true; _t._fire("onPause", [_t.curve.getValue(_t._getStep())]); return _t; // dojo.Animation }, gotoPercent: function(/*Decimal*/ percent, /*Boolean?*/ andPlay){ // summary: // Sets the progress of the animation. // percent: // A percentage in decimal notation (between and including 0.0 and 1.0). // andPlay: // If true, play the animation after setting the progress. var _t = this; _t._stopTimer(); _t._active = _t._paused = true; _t._percent = percent; if(andPlay){ _t.play(); } return _t; // dojo.Animation }, stop: function(/*boolean?*/ gotoEnd){ // summary: Stops a running animation. // gotoEnd: If true, the animation will end. var _t = this; if(_t._delayTimer){ _t._clearTimer(); } if(!_t._timer){ return _t; /* dojo.Animation */ } _t._stopTimer(); if(gotoEnd){ _t._percent = 1; } _t._fire("onStop", [_t.curve.getValue(_t._getStep())]); _t._active = _t._paused = false; return _t; // dojo.Animation }, status: function(){ // summary: // Returns a string token representation of the status of // the animation, one of: "paused", "playing", "stopped" if(this._active){ return this._paused ? "paused" : "playing"; // String } return "stopped"; // String }, _cycle: function(){ var _t = this; if(_t._active){ var curr = new Date().valueOf(); var step = (curr - _t._startTime) / (_t.duration); if(step >= 1){ step = 1; } _t._percent = step; // Perform easing if(_t.easing){ step = _t.easing(step); } _t._fire("onAnimate", [_t.curve.getValue(step)]); if(_t._percent < 1){ _t._startTimer(); }else{ _t._active = false; if(_t.repeat > 0){ _t.repeat--; _t.play(null, true); }else if(_t.repeat == -1){ _t.play(null, true); }else{ if(_t._startRepeatCount){ _t.repeat = _t._startRepeatCount; _t._startRepeatCount = 0; } } _t._percent = 0; _t._fire("onEnd", [_t.node]); !_t.repeat && _t._stopTimer(); } } return _t; // dojo.Animation }, _clearTimer: function(){ // summary: Clear the play delay timer clearTimeout(this._delayTimer); delete this._delayTimer; } }); // the local timer, stubbed into all Animation instances var ctr = 0, timer = null, runner = { run: function(){} }; lang.extend(dojo.Animation, { _startTimer: function(){ if(!this._timer){ this._timer = connect.connect(runner, "run", this, "_cycle"); ctr++; } if(!timer){ timer = setInterval(lang.hitch(runner, "run"), this.rate); } }, _stopTimer: function(){ if(this._timer){ connect.disconnect(this._timer); this._timer = null; ctr--; } if(ctr <= 0){ clearInterval(timer); timer = null; ctr = 0; } } }); var _makeFadeable = has("ie") ? function(node){ // only set the zoom if the "tickle" value would be the same as the // default var ns = node.style; // don't set the width to auto if it didn't already cascade that way. // We don't want to f anyones designs if(!ns.width.length && style.get(node, "width") == "auto"){ ns.width = "auto"; } } : function(){}; dojo._fade = function(/*Object*/ args){ // summary: // Returns an animation that will fade the node defined by // args.node from the start to end values passed (args.start // args.end) (end is mandatory, start is optional) args.node = dom.byId(args.node); var fArgs = _mixin({ properties: {} }, args), props = (fArgs.properties.opacity = {}); props.start = !("start" in fArgs) ? function(){ return +style.get(fArgs.node, "opacity")||0; } : fArgs.start; props.end = fArgs.end; var anim = dojo.animateProperty(fArgs); connect.connect(anim, "beforeBegin", lang.partial(_makeFadeable, fArgs.node)); return anim; // dojo.Animation }; /*===== dojo.__FadeArgs = function(node, duration, easing){ // node: DOMNode|String // The node referenced in the animation // duration: Integer? // Duration of the animation in milliseconds. // easing: Function? // An easing function. this.node = node; this.duration = duration; this.easing = easing; } =====*/ dojo.fadeIn = function(/*dojo.__FadeArgs*/ args){ // summary: // Returns an animation that will fade node defined in 'args' from // its current opacity to fully opaque. return dojo._fade(_mixin({ end: 1 }, args)); // dojo.Animation }; dojo.fadeOut = function(/*dojo.__FadeArgs*/ args){ // summary: // Returns an animation that will fade node defined in 'args' // from its current opacity to fully transparent. return dojo._fade(_mixin({ end: 0 }, args)); // dojo.Animation }; dojo._defaultEasing = function(/*Decimal?*/ n){ // summary: The default easing function for dojo.Animation(s) return 0.5 + ((Math.sin((n + 1.5) * Math.PI)) / 2); // Decimal }; var PropLine = function(properties){ // PropLine is an internal class which is used to model the values of // an a group of CSS properties across an animation lifecycle. In // particular, the "getValue" function handles getting interpolated // values between start and end for a particular CSS value. this._properties = properties; for(var p in properties){ var prop = properties[p]; if(prop.start instanceof Color){ // create a reusable temp color object to keep intermediate results prop.tempColor = new Color(); } } }; PropLine.prototype.getValue = function(r){ var ret = {}; for(var p in this._properties){ var prop = this._properties[p], start = prop.start; if(start instanceof Color){ ret[p] = Color.blendColors(start, prop.end, r, prop.tempColor).toCss(); }else if(!lang.isArray(start)){ ret[p] = ((prop.end - start) * r) + start + (p != "opacity" ? prop.units || "px" : 0); } } return ret; }; /*===== dojo.declare("dojo.__AnimArgs", [dojo.__FadeArgs], { // Properties: Object? // A hash map of style properties to Objects describing the transition, // such as the properties of dojo._Line with an additional 'units' property properties: {} //TODOC: add event callbacks }); =====*/ dojo.animateProperty = function(/*dojo.__AnimArgs*/ args){ // summary: // Returns an animation that will transition the properties of // node defined in `args` depending how they are defined in // `args.properties` // // description: // `dojo.animateProperty` is the foundation of most `dojo.fx` // animations. It takes an object of "properties" corresponding to // style properties, and animates them in parallel over a set // duration. // // example: // A simple animation that changes the width of the specified node. // | dojo.animateProperty({ // | node: "nodeId", // | properties: { width: 400 }, // | }).play(); // Dojo figures out the start value for the width and converts the // integer specified for the width to the more expressive but // verbose form `{ width: { end: '400', units: 'px' } }` which you // can also specify directly. Defaults to 'px' if ommitted. // // example: // Animate width, height, and padding over 2 seconds... the // pedantic way: // | dojo.animateProperty({ node: node, duration:2000, // | properties: { // | width: { start: '200', end: '400', units:"px" }, // | height: { start:'200', end: '400', units:"px" }, // | paddingTop: { start:'5', end:'50', units:"px" } // | } // | }).play(); // Note 'paddingTop' is used over 'padding-top'. Multi-name CSS properties // are written using "mixed case", as the hyphen is illegal as an object key. // // example: // Plug in a different easing function and register a callback for // when the animation ends. Easing functions accept values between // zero and one and return a value on that basis. In this case, an // exponential-in curve. // | dojo.animateProperty({ // | node: "nodeId", // | // dojo figures out the start value // | properties: { width: { end: 400 } }, // | easing: function(n){ // | return (n==0) ? 0 : Math.pow(2, 10 * (n - 1)); // | }, // | onEnd: function(node){ // | // called when the animation finishes. The animation // | // target is passed to this function // | } // | }).play(500); // delay playing half a second // // example: // Like all `dojo.Animation`s, animateProperty returns a handle to the // Animation instance, which fires the events common to Dojo FX. Use `dojo.connect` // to access these events outside of the Animation definiton: // | var anim = dojo.animateProperty({ // | node:"someId", // | properties:{ // | width:400, height:500 // | } // | }); // | dojo.connect(anim,"onEnd", function(){ // | console.log("animation ended"); // | }); // | // play the animation now: // | anim.play(); // // example: // Each property can be a function whose return value is substituted along. // Additionally, each measurement (eg: start, end) can be a function. The node // reference is passed direcly to callbacks. // | dojo.animateProperty({ // | node:"mine", // | properties:{ // | height:function(node){ // | // shrink this node by 50% // | return dojo.position(node).h / 2 // | }, // | width:{ // | start:function(node){ return 100; }, // | end:function(node){ return 200; } // | } // | } // | }).play(); // var n = args.node = dom.byId(args.node); if(!args.easing){ args.easing = dojo._defaultEasing; } var anim = new dojo.Animation(args); connect.connect(anim, "beforeBegin", anim, function(){ var pm = {}; for(var p in this.properties){ // Make shallow copy of properties into pm because we overwrite // some values below. In particular if start/end are functions // we don't want to overwrite them or the functions won't be // called if the animation is reused. if(p == "width" || p == "height"){ this.node.display = "block"; } var prop = this.properties[p]; if(lang.isFunction(prop)){ prop = prop(n); } prop = pm[p] = _mixin({}, (lang.isObject(prop) ? prop: { end: prop })); if(lang.isFunction(prop.start)){ prop.start = prop.start(n); } if(lang.isFunction(prop.end)){ prop.end = prop.end(n); } var isColor = (p.toLowerCase().indexOf("color") >= 0); function getStyle(node, p){ // dojo.style(node, "height") can return "auto" or "" on IE; this is more reliable: var v = { height: node.offsetHeight, width: node.offsetWidth }[p]; if(v !== undefined){ return v; } v = style.get(node, p); return (p == "opacity") ? +v : (isColor ? v : parseFloat(v)); } if(!("end" in prop)){ prop.end = getStyle(n, p); }else if(!("start" in prop)){ prop.start = getStyle(n, p); } if(isColor){ prop.start = new Color(prop.start); prop.end = new Color(prop.end); }else{ prop.start = (p == "opacity") ? +prop.start : parseFloat(prop.start); } } this.curve = new PropLine(pm); }); connect.connect(anim, "onAnimate", lang.hitch(style, "set", anim.node)); return anim; // dojo.Animation }; dojo.anim = function( /*DOMNode|String*/ node, /*Object*/ properties, /*Integer?*/ duration, /*Function?*/ easing, /*Function?*/ onEnd, /*Integer?*/ delay){ // summary: // A simpler interface to `dojo.animateProperty()`, also returns // an instance of `dojo.Animation` but begins the animation // immediately, unlike nearly every other Dojo animation API. // description: // `dojo.anim` is a simpler (but somewhat less powerful) version // of `dojo.animateProperty`. It uses defaults for many basic properties // and allows for positional parameters to be used in place of the // packed "property bag" which is used for other Dojo animation // methods. // // The `dojo.Animation` object returned from `dojo.anim` will be // already playing when it is returned from this function, so // calling play() on it again is (usually) a no-op. // node: // a DOM node or the id of a node to animate CSS properties on // duration: // The number of milliseconds over which the animation // should run. Defaults to the global animation default duration // (350ms). // easing: // An easing function over which to calculate acceleration // and deceleration of the animation through its duration. // A default easing algorithm is provided, but you may // plug in any you wish. A large selection of easing algorithms // are available in `dojo.fx.easing`. // onEnd: // A function to be called when the animation finishes // running. // delay: // The number of milliseconds to delay beginning the // animation by. The default is 0. // example: // Fade out a node // | dojo.anim("id", { opacity: 0 }); // example: // Fade out a node over a full second // | dojo.anim("id", { opacity: 0 }, 1000); return dojo.animateProperty({ // dojo.Animation node: node, duration: duration || dojo.Animation.prototype.duration, properties: properties, easing: easing, onEnd: onEnd }).play(delay || 0); }; return { _Line: dojo._Line, Animation: dojo.Animation, _fade: dojo._fade, fadeIn: dojo.fadeIn, fadeOut: dojo.fadeOut, _defaultEasing: dojo._defaultEasing, animateProperty: dojo.animateProperty, anim: dojo.anim }; });