Форк Rambox
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.
 
 
 

512 lines
15 KiB

/**
* This class manages animation for a specific {@link #target}. The animation allows
* animation of various properties on the target, such as size, position, color and others.
*
* ## Starting Conditions
*
* The starting conditions for the animation are provided by the {@link #from} configuration.
* Any/all of the properties in the {@link #from} configuration can be specified. If a particular
* property is not defined, the starting value for that property will be read directly from the target.
*
* ## End Conditions
*
* The ending conditions for the animation are provided by the {@link #to} configuration. These mark
* the final values once the animations has finished. The values in the {@link #from} can mirror
* those in the {@link #to} configuration to provide a starting point.
*
* ## Other Options
*
* - {@link #duration}: Specifies the time period of the animation.
* - {@link #easing}: Specifies the easing of the animation.
* - {@link #iterations}: Allows the animation to repeat a number of times.
* - {@link #alternate}: Used in conjunction with {@link #iterations}, reverses the direction every second iteration.
*
* ## Example Code
*
* @example
* var myComponent = Ext.create('Ext.Component', {
* renderTo: document.body,
* width: 200,
* height: 200,
* style: 'border: 1px solid red;'
* });
*
* Ext.create('Ext.fx.Anim', {
* target: myComponent,
* duration: 1000,
* from: {
* width: 400 //starting width 400
* },
* to: {
* width: 300, //end width 300
* height: 300 // end height 300
* }
* });
*/
Ext.define('Ext.fx.Anim', {
/* Begin Definitions */
mixins: {
observable: 'Ext.util.Observable'
},
requires: ['Ext.fx.Manager', 'Ext.fx.Animator', 'Ext.fx.Easing', 'Ext.fx.CubicBezier', 'Ext.fx.PropertyHandler'],
/* End Definitions */
/**
* @property {Boolean} isAnimation
* `true` in this class to identify an object as an instantiated Anim, or subclass thereof.
*/
isAnimation: true,
/**
* @cfg {Function/String} callback
* A function to be run after the animation has completed.
* @declarativeHandler
*/
/**
* @cfg {Object} scope
* The scope that the {@link #callback} function will be called with
*/
/**
* @cfg {Boolean} remove
* `true` to remove the target when the animation is complete, using the appropriate removal
* method for the target. For example, a component will be destroyed, elements will be removed.
*/
/**
* @cfg {Number} duration
* Time in milliseconds for a single animation to last. If the {@link #iterations} property is
* specified, then each animate will take the same duration for each iteration.
*/
duration: 250,
/**
* @cfg {Number} delay
* Time to delay before starting the animation.
*/
delay: 0,
/* @private used to track a delayed starting time */
delayStart: 0,
/**
* @cfg {Boolean} dynamic
* Currently only for Component Animation: Only set a component's outer element size bypassing layouts.
* Set to true to do full layouts for every frame of the animation.
*/
dynamic: false,
/**
* @cfg {String} easing
* This describes how the intermediate values used during a transition will be calculated.
* It allows for a transition to change speed over its duration.
*
* - backIn
* - backOut
* - bounceIn
* - bounceOut
* - ease
* - easeIn
* - easeOut
* - easeInOut
* - elasticIn
* - elasticOut
* - cubic-bezier(x1, y1, x2, y2)
*
* Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
* specification. The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
* be in the range [0, 1] or the definition is invalid.
*
* [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
*/
easing: 'ease',
/**
* @cfg {Object} keyframes
* Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
* is considered '100%'. **Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
* "from" or "to".** A keyframe declaration without these keyframe selectors is invalid and will not be available for
* animation. The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
* be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
*
* keyframes : {
* '0%': {
* left: 100
* },
* '40%': {
* left: 150
* },
* '60%': {
* left: 75
* },
* '100%': {
* left: 100
* }
* }
*/
/**
* @private
*/
damper: 1,
/**
* @private
*/
bezierRE: /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
/**
* Run the animation from the end to the beginning
* Defaults to false.
* @cfg {Boolean} reverse
*/
reverse: false,
/**
* Flag to determine if the animation has started
* @property running
* @type Boolean
*/
running: false,
/**
* Flag to determine if the animation is paused. Only set this to true if you need to
* keep the Anim instance around to be un-paused later; otherwise call {@link #end}.
* @property paused
* @type Boolean
*/
paused: false,
/**
* @cfg {Number} iterations
* Number of times to execute the animation.
*/
iterations: 1,
/**
* @cfg {Boolean} autoEnd
* `true` to immediately force this animation to its final state. This can be useful
* in cases where you want the final effect of an animation, but need to the actual
* animation dynamically. Also see the {@link #jumpToEnd} method.
*/
autoEnd: false,
/**
* @cfg {Boolean} alternate
* Used in conjunction with iterations to reverse the animation each time an iteration completes.
*/
alternate: false,
/**
* Current iteration the animation is running.
* @property currentIteration
* @type Number
*/
currentIteration: 0,
/**
* Starting time of the animation.
* @property startTime
* @type Date
*/
startTime: 0,
/**
* Contains a cache of the interpolators to be used.
* @private
* @property propHandlers
* @type Object
*/
/**
* @cfg {String/Object} target
* The {@link Ext.fx.target.Target} to apply the animation to. This should only be specified when creating an Ext.fx.Anim directly.
* The target does not need to be a {@link Ext.fx.target.Target} instance, it can be the underlying object. For example, you can
* pass a Component, Element or Sprite as the target and the Anim will create the appropriate {@link Ext.fx.target.Target} object
* automatically.
*/
/**
* @cfg {Object} from
* An object containing property/value pairs for the beginning of the animation. If not specified, the current state of the
* Ext.fx.target will be used. For example:
*
* from: {
* opacity: 0, // Transparent
* color: '#ffffff', // White
* left: 0
* }
*
*/
/**
* @cfg {Object} to (required)
* An object containing property/value pairs for the end of the animation. For example:
*
* to: {
* opacity: 1, // Opaque
* color: '#00ff00', // Green
* left: 500
* }
*
*/
// @private
frameCount: 0,
/**
* @event beforeanimate
* Fires before the animation starts. A handler can return false to cancel the animation.
* @param {Ext.fx.Anim} this
*/
/**
* @event afteranimate
* Fires when the animation is complete.
* @param {Ext.fx.Anim} this
* @param {Date} startTime
*/
/**
* @event lastframe
* Fires when the animation's last frame has been set.
* @param {Ext.fx.Anim} this
* @param {Date} startTime
*/
// @private
constructor: function(config) {
var me = this,
curve;
config = config || {};
// If keyframes are passed, they really want an Animator instead.
if (config.keyframes) {
return new Ext.fx.Animator(config);
}
Ext.apply(me, config);
if (me.from === undefined) {
me.from = {};
}
me.propHandlers = {};
me.config = config;
me.target = Ext.fx.Manager.createTarget(me.target);
me.easingFn = Ext.fx.Easing[me.easing];
me.target.dynamic = me.dynamic;
// If not a pre-defined curve, try a cubic-bezier
if (!me.easingFn) {
me.easingFn = String(me.easing).match(me.bezierRE);
if (me.easingFn && me.easingFn.length === 5) {
curve = me.easingFn;
me.easingFn = Ext.fx.CubicBezier.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]);
}
}
me.id = Ext.id(null, 'ext-anim-');
me.mixins.observable.constructor.call(me);
Ext.fx.Manager.addAnim(me);
if (config.autoEnd) {
me.running = true;
me.jumpToEnd();
}
},
/**
* @private
* Helper to the target
*/
setAttr: function(attr, value) {
return Ext.fx.Manager.items.get(this.id).setAttr(this.target, attr, value);
},
/**
* @private
* Set up the initial currentAttrs hash.
*/
initAttrs: function() {
var me = this,
from = me.from,
to = me.to,
initialFrom = me.initialFrom || {},
out = {},
start, end, propHandler, attr;
for (attr in to) {
if (to.hasOwnProperty(attr)) {
start = me.target.getAttr(attr, from[attr]);
end = to[attr];
// Use default (numeric) property handler
if (!Ext.fx.PropertyHandler[attr]) {
if (Ext.isObject(end)) {
propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.object;
} else {
propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.defaultHandler;
}
}
// Use custom handler
else {
propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler[attr];
}
out[attr] = propHandler.get(start, end, me.damper, initialFrom[attr], attr);
}
}
me.currentAttrs = out;
},
/**
* @private
* Fires beforeanimate and sets the running flag.
*/
start: function(startTime) {
var me = this,
delay = me.delay,
delayStart = me.delayStart,
delayDelta;
if (delay) {
if (!delayStart) {
me.delayStart = startTime;
return;
}
else {
delayDelta = startTime - delayStart;
if (delayDelta < delay) {
return;
}
else {
// Compensate for frame delay;
startTime = new Date(delayStart.getTime() + delay);
}
}
}
if (me.fireEvent('beforeanimate', me) !== false) {
me.startTime = startTime;
if (!me.paused && !me.currentAttrs) {
me.initAttrs();
}
me.running = true;
me.frameCount = 0;
}
},
/**
* Immediately force this animation to its final state.
*/
jumpToEnd: function(){
var me = this;
if (!me.endWasCalled) {
if (!me.currentAttrs) {
me.initAttrs();
}
Ext.fx.Manager.jumpToEnd(me);
me.end();
}
},
/**
* @private
* Calculate attribute value at the passed timestamp.
* @return a hash of the new attributes.
*/
runAnim: function(elapsedTime) {
var me = this,
attrs = me.currentAttrs,
duration = me.duration,
easingFn = me.easingFn,
propHandlers = me.propHandlers,
ret = {},
easing, values, attr, lastFrame;
if (elapsedTime >= duration) {
elapsedTime = duration;
lastFrame = true;
}
if (me.reverse) {
elapsedTime = duration - elapsedTime;
}
for (attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
values = attrs[attr];
easing = lastFrame ? 1 : easingFn(elapsedTime / duration);
ret[attr] = propHandlers[attr].set(values, easing);
}
}
me.frameCount++;
return ret;
},
/**
* @private
* Perform lastFrame cleanup and handle iterations
* @return a hash of the new attributes.
*/
lastFrame: function() {
var me = this,
iter = me.iterations,
iterCount = me.currentIteration;
iterCount++;
if (iterCount < iter) {
if (me.alternate) {
me.reverse = !me.reverse;
}
me.startTime = new Date();
me.currentIteration = iterCount;
// Turn off paused for CSS3 Transitions
me.paused = false;
}
else {
me.currentIteration = 0;
me.end();
me.fireEvent('lastframe', me, me.startTime);
}
},
endWasCalled: 0,
/**
* Fire afteranimate event and end the animation. Usually called automatically when the
* animation reaches its final frame, but can also be called manually to preemptively
* stop and destroy the running animation.
*/
end: function() {
var me = this;
if (me.endWasCalled++) {
return;
}
me.startTime = 0;
me.paused = false;
me.running = false;
Ext.fx.Manager.removeAnim(me);
me.fireEvent('afteranimate', me, me.startTime);
Ext.callback(me.callback, me.scope, [me, me.startTime]);
if (me.remove) {
me.target.destroy();
}
},
isReady: function() {
return this.paused === false && this.running === false && this.iterations > 0;
},
isRunning: function() {
return this.paused === false && this.running === true && this.isAnimator !== true;
}
});
/**
* @member Ext
* @property {Boolean} enableFx
* True if the {@link Ext.fx.Anim} Class is available.
*/
Ext.enableFx = true; // Indicate that Fx is available. Class might not be available immediately.