You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
676 lines
19 KiB
676 lines
19 KiB
define(["./kernel", "./config", /*===== "./declare", =====*/ "./lang", "../Evented", "./Color", "../aspect", "../sniff", "../dom", "../dom-style"], |
|
function(dojo, config, /*===== declare, =====*/ lang, Evented, Color, aspect, has, dom, style){ |
|
// module: |
|
// dojo/_base/fx |
|
// 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; |
|
|
|
// Module export |
|
var basefx = { |
|
// summary: |
|
// This module defines the base dojo/_base/fx implementation. |
|
}; |
|
|
|
var _Line = basefx._Line = function(/*int*/ start, /*int*/ end){ |
|
// summary: |
|
// 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; |
|
}; |
|
|
|
_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 |
|
}; |
|
|
|
var Animation = basefx.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 `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 _Line(this.curve[0], this.curve[1]); |
|
} |
|
|
|
}; |
|
Animation.prototype = new Evented(); |
|
|
|
lang.extend(Animation, { |
|
// duration: Integer |
|
// The time in milliseconds the animation will take to run |
|
duration: 350, |
|
|
|
/*===== |
|
// curve: _Line|Array |
|
// A two element array of start and end values, or a `_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 _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 Animation begins playing (synchronous) |
|
beforeBegin: null, |
|
|
|
// onBegin: Event? |
|
// Synthetic event fired as a Animation begins playing (useful?) |
|
onBegin: null, |
|
|
|
// onAnimate: Event? |
|
// Synthetic event fired at each interval of the Animation |
|
onAnimate: null, |
|
|
|
// onEnd: Event? |
|
// Synthetic event fired after the final frame of the Animation |
|
onEnd: null, |
|
|
|
// onPlay: Event? |
|
// Synthetic event fired any time the Animation is play()'ed |
|
onPlay: null, |
|
|
|
// onPause: Event? |
|
// Synthetic event fired when the Animation is paused |
|
onPause: null, |
|
|
|
// onStop: Event |
|
// Synthetic event fires when the 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 this Animation |
|
// instance. |
|
// evt: |
|
// The event to fire. |
|
// args: |
|
// The arguments to pass to the event. |
|
var a = args||[]; |
|
if(this[evt]){ |
|
if(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; // 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: 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; // 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; // Animation |
|
}, |
|
|
|
pause: function(){ |
|
// summary: |
|
// Pauses a running animation. |
|
var _t = this; |
|
if(_t._delayTimer){ _t._clearTimer(); } |
|
_t._stopTimer(); |
|
if(!_t._active){ return _t; /*Animation*/ } |
|
_t._paused = true; |
|
_t._fire("onPause", [_t.curve.getValue(_t._getStep())]); |
|
return _t; // 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; // 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; /* Animation */ } |
|
_t._stopTimer(); |
|
if(gotoEnd){ |
|
_t._percent = 1; |
|
} |
|
_t._fire("onStop", [_t.curve.getValue(_t._getStep())]); |
|
_t._active = _t._paused = false; |
|
return _t; // Animation |
|
}, |
|
|
|
destroy: function(){ |
|
// summary: |
|
// cleanup the animation |
|
this.stop(); |
|
}, |
|
|
|
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(); |
|
// Allow durations of 0 (instant) by setting step to 1 - see #13798 |
|
var step = _t.duration === 0 ? 1 : (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; // 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(Animation, { |
|
|
|
_startTimer: function(){ |
|
if(!this._timer){ |
|
this._timer = aspect.after(runner, "run", lang.hitch(this, "_cycle"), true); |
|
ctr++; |
|
} |
|
if(!timer){ |
|
timer = setInterval(lang.hitch(runner, "run"), this.rate); |
|
} |
|
}, |
|
|
|
_stopTimer: function(){ |
|
if(this._timer){ |
|
this._timer.remove(); |
|
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(){}; |
|
|
|
basefx._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 = basefx.animateProperty(fArgs); |
|
aspect.after(anim, "beforeBegin", lang.partial(_makeFadeable, fArgs.node), true); |
|
|
|
return anim; // Animation |
|
}; |
|
|
|
/*===== |
|
var __FadeArgs = declare(null, { |
|
// node: DOMNode|String |
|
// The node referenced in the animation |
|
// duration: Integer? |
|
// Duration of the animation in milliseconds. |
|
// easing: Function? |
|
// An easing function. |
|
}); |
|
=====*/ |
|
|
|
basefx.fadeIn = function(/*__FadeArgs*/ args){ |
|
// summary: |
|
// Returns an animation that will fade node defined in 'args' from |
|
// its current opacity to fully opaque. |
|
return basefx._fade(_mixin({ end: 1 }, args)); // Animation |
|
}; |
|
|
|
basefx.fadeOut = function(/*__FadeArgs*/ args){ |
|
// summary: |
|
// Returns an animation that will fade node defined in 'args' |
|
// from its current opacity to fully transparent. |
|
return basefx._fade(_mixin({ end: 0 }, args)); // Animation |
|
}; |
|
|
|
basefx._defaultEasing = function(/*Decimal?*/ n){ |
|
// summary: |
|
// The default easing function for 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; |
|
}; |
|
|
|
/*===== |
|
var __AnimArgs = declare(__FadeArgs, { |
|
// properties: Object? |
|
// A hash map of style properties to Objects describing the transition, |
|
// such as the properties of _Line with an additional 'units' property |
|
properties: {} |
|
|
|
//TODOC: add event callbacks |
|
}); |
|
=====*/ |
|
|
|
basefx.animateProperty = function(/*__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: |
|
// Foundation of most `dojo/_base/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. |
|
// | basefx.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 omitted. |
|
// |
|
// example: |
|
// Animate width, height, and padding over 2 seconds... the |
|
// pedantic way: |
|
// | basefx.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. |
|
// | basefx.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 `Animation`s, animateProperty returns a handle to the |
|
// Animation instance, which fires the events common to Dojo FX. Use `aspect.after` |
|
// to access these events outside of the Animation definition: |
|
// | var anim = basefx.animateProperty({ |
|
// | node:"someId", |
|
// | properties:{ |
|
// | width:400, height:500 |
|
// | } |
|
// | }); |
|
// | aspect.after(anim, "onEnd", function(){ |
|
// | console.log("animation ended"); |
|
// | }, true); |
|
// | // 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 directly to callbacks. |
|
// | basefx.animateProperty({ |
|
// | node:"mine", |
|
// | properties:{ |
|
// | height:function(node){ |
|
// | // shrink this node by 50% |
|
// | return domGeom.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 Animation(args); |
|
aspect.after(anim, "beforeBegin", lang.hitch(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); |
|
var getStyle = function getStyle(node, p){ |
|
// domStyle.get(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); |
|
}), true); |
|
aspect.after(anim, "onAnimate", lang.hitch(style, "set", anim.node), true); |
|
return anim; // Animation |
|
}; |
|
|
|
basefx.anim = function( /*DOMNode|String*/ node, |
|
/*Object*/ properties, |
|
/*Integer?*/ duration, |
|
/*Function?*/ easing, |
|
/*Function?*/ onEnd, |
|
/*Integer?*/ delay){ |
|
// summary: |
|
// A simpler interface to `animateProperty()`, also returns |
|
// an instance of `Animation` but begins the animation |
|
// immediately, unlike nearly every other Dojo animation API. |
|
// description: |
|
// Simpler (but somewhat less powerful) version |
|
// of `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 `Animation` object returned will be already playing, 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 |
|
// | basefx.anim("id", { opacity: 0 }); |
|
// example: |
|
// Fade out a node over a full second |
|
// | basefx.anim("id", { opacity: 0 }, 1000); |
|
return basefx.animateProperty({ // Animation |
|
node: node, |
|
duration: duration || Animation.prototype.duration, |
|
properties: properties, |
|
easing: easing, |
|
onEnd: onEnd |
|
}).play(delay || 0); |
|
}; |
|
|
|
|
|
if(has("extend-dojo")){ |
|
_mixin(dojo, basefx); |
|
// Alias to drop come 2.0: |
|
dojo._Animation = Animation; |
|
} |
|
|
|
return basefx; |
|
});
|
|
|