275 lines
7.2 KiB
JavaScript
275 lines
7.2 KiB
JavaScript
var Animator = require("../animation/Animator");
|
|
|
|
var logError = require("../core/log");
|
|
|
|
var _util = require("../core/util");
|
|
|
|
var isString = _util.isString;
|
|
var isFunction = _util.isFunction;
|
|
var isObject = _util.isObject;
|
|
var isArrayLike = _util.isArrayLike;
|
|
var indexOf = _util.indexOf;
|
|
|
|
/**
|
|
* @alias module:zrender/mixin/Animatable
|
|
* @constructor
|
|
*/
|
|
var Animatable = function () {
|
|
/**
|
|
* @type {Array.<module:zrender/animation/Animator>}
|
|
* @readOnly
|
|
*/
|
|
this.animators = [];
|
|
};
|
|
|
|
Animatable.prototype = {
|
|
constructor: Animatable,
|
|
|
|
/**
|
|
* 动画
|
|
*
|
|
* @param {string} path The path to fetch value from object, like 'a.b.c'.
|
|
* @param {boolean} [loop] Whether to loop animation.
|
|
* @return {module:zrender/animation/Animator}
|
|
* @example:
|
|
* el.animate('style', false)
|
|
* .when(1000, {x: 10} )
|
|
* .done(function(){ // Animation done })
|
|
* .start()
|
|
*/
|
|
animate: function (path, loop) {
|
|
var target;
|
|
var animatingShape = false;
|
|
var el = this;
|
|
var zr = this.__zr;
|
|
|
|
if (path) {
|
|
var pathSplitted = path.split('.');
|
|
var prop = el; // If animating shape
|
|
|
|
animatingShape = pathSplitted[0] === 'shape';
|
|
|
|
for (var i = 0, l = pathSplitted.length; i < l; i++) {
|
|
if (!prop) {
|
|
continue;
|
|
}
|
|
|
|
prop = prop[pathSplitted[i]];
|
|
}
|
|
|
|
if (prop) {
|
|
target = prop;
|
|
}
|
|
} else {
|
|
target = el;
|
|
}
|
|
|
|
if (!target) {
|
|
logError('Property "' + path + '" is not existed in element ' + el.id);
|
|
return;
|
|
}
|
|
|
|
var animators = el.animators;
|
|
var animator = new Animator(target, loop);
|
|
animator.during(function (target) {
|
|
el.dirty(animatingShape);
|
|
}).done(function () {
|
|
// FIXME Animator will not be removed if use `Animator#stop` to stop animation
|
|
animators.splice(indexOf(animators, animator), 1);
|
|
});
|
|
animators.push(animator); // If animate after added to the zrender
|
|
|
|
if (zr) {
|
|
zr.animation.addAnimator(animator);
|
|
}
|
|
|
|
return animator;
|
|
},
|
|
|
|
/**
|
|
* 停止动画
|
|
* @param {boolean} forwardToLast If move to last frame before stop
|
|
*/
|
|
stopAnimation: function (forwardToLast) {
|
|
var animators = this.animators;
|
|
var len = animators.length;
|
|
|
|
for (var i = 0; i < len; i++) {
|
|
animators[i].stop(forwardToLast);
|
|
}
|
|
|
|
animators.length = 0;
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Caution: this method will stop previous animation.
|
|
* So do not use this method to one element twice before
|
|
* animation starts, unless you know what you are doing.
|
|
* @param {Object} target
|
|
* @param {number} [time=500] Time in ms
|
|
* @param {string} [easing='linear']
|
|
* @param {number} [delay=0]
|
|
* @param {Function} [callback]
|
|
* @param {Function} [forceAnimate] Prevent stop animation and callback
|
|
* immediently when target values are the same as current values.
|
|
*
|
|
* @example
|
|
* // Animate position
|
|
* el.animateTo({
|
|
* position: [10, 10]
|
|
* }, function () { // done })
|
|
*
|
|
* // Animate shape, style and position in 100ms, delayed 100ms, with cubicOut easing
|
|
* el.animateTo({
|
|
* shape: {
|
|
* width: 500
|
|
* },
|
|
* style: {
|
|
* fill: 'red'
|
|
* }
|
|
* position: [10, 10]
|
|
* }, 100, 100, 'cubicOut', function () { // done })
|
|
*/
|
|
// TODO Return animation key
|
|
animateTo: function (target, time, delay, easing, callback, forceAnimate) {
|
|
animateTo(this, target, time, delay, easing, callback, forceAnimate);
|
|
},
|
|
|
|
/**
|
|
* Animate from the target state to current state.
|
|
* The params and the return value are the same as `this.animateTo`.
|
|
*/
|
|
animateFrom: function (target, time, delay, easing, callback, forceAnimate) {
|
|
animateTo(this, target, time, delay, easing, callback, forceAnimate, true);
|
|
}
|
|
};
|
|
|
|
function animateTo(animatable, target, time, delay, easing, callback, forceAnimate, reverse) {
|
|
// animateTo(target, time, easing, callback);
|
|
if (isString(delay)) {
|
|
callback = easing;
|
|
easing = delay;
|
|
delay = 0;
|
|
} // animateTo(target, time, delay, callback);
|
|
else if (isFunction(easing)) {
|
|
callback = easing;
|
|
easing = 'linear';
|
|
delay = 0;
|
|
} // animateTo(target, time, callback);
|
|
else if (isFunction(delay)) {
|
|
callback = delay;
|
|
delay = 0;
|
|
} // animateTo(target, callback)
|
|
else if (isFunction(time)) {
|
|
callback = time;
|
|
time = 500;
|
|
} // animateTo(target)
|
|
else if (!time) {
|
|
time = 500;
|
|
} // Stop all previous animations
|
|
|
|
|
|
animatable.stopAnimation();
|
|
animateToShallow(animatable, '', animatable, target, time, delay, reverse); // Animators may be removed immediately after start
|
|
// if there is nothing to animate
|
|
|
|
var animators = animatable.animators.slice();
|
|
var count = animators.length;
|
|
|
|
function done() {
|
|
count--;
|
|
|
|
if (!count) {
|
|
callback && callback();
|
|
}
|
|
} // No animators. This should be checked before animators[i].start(),
|
|
// because 'done' may be executed immediately if no need to animate.
|
|
|
|
|
|
if (!count) {
|
|
callback && callback();
|
|
} // Start after all animators created
|
|
// Incase any animator is done immediately when all animation properties are not changed
|
|
|
|
|
|
for (var i = 0; i < animators.length; i++) {
|
|
animators[i].done(done).start(easing, forceAnimate);
|
|
}
|
|
}
|
|
/**
|
|
* @param {string} path=''
|
|
* @param {Object} source=animatable
|
|
* @param {Object} target
|
|
* @param {number} [time=500]
|
|
* @param {number} [delay=0]
|
|
* @param {boolean} [reverse] If `true`, animate
|
|
* from the `target` to current state.
|
|
*
|
|
* @example
|
|
* // Animate position
|
|
* el._animateToShallow({
|
|
* position: [10, 10]
|
|
* })
|
|
*
|
|
* // Animate shape, style and position in 100ms, delayed 100ms
|
|
* el._animateToShallow({
|
|
* shape: {
|
|
* width: 500
|
|
* },
|
|
* style: {
|
|
* fill: 'red'
|
|
* }
|
|
* position: [10, 10]
|
|
* }, 100, 100)
|
|
*/
|
|
|
|
|
|
function animateToShallow(animatable, path, source, target, time, delay, reverse) {
|
|
var objShallow = {};
|
|
var propertyCount = 0;
|
|
|
|
for (var name in target) {
|
|
if (!target.hasOwnProperty(name)) {
|
|
continue;
|
|
}
|
|
|
|
if (source[name] != null) {
|
|
if (isObject(target[name]) && !isArrayLike(target[name])) {
|
|
animateToShallow(animatable, path ? path + '.' + name : name, source[name], target[name], time, delay, reverse);
|
|
} else {
|
|
if (reverse) {
|
|
objShallow[name] = source[name];
|
|
setAttrByPath(animatable, path, name, target[name]);
|
|
} else {
|
|
objShallow[name] = target[name];
|
|
}
|
|
|
|
propertyCount++;
|
|
}
|
|
} else if (target[name] != null && !reverse) {
|
|
setAttrByPath(animatable, path, name, target[name]);
|
|
}
|
|
}
|
|
|
|
if (propertyCount > 0) {
|
|
animatable.animate(path, false).when(time == null ? 500 : time, objShallow).delay(delay || 0);
|
|
}
|
|
}
|
|
|
|
function setAttrByPath(el, path, name, value) {
|
|
// Attr directly if not has property
|
|
// FIXME, if some property not needed for element ?
|
|
if (!path) {
|
|
el.attr(name, value);
|
|
} else {
|
|
// Only support set shape or style
|
|
var props = {};
|
|
props[path] = {};
|
|
props[path][name] = value;
|
|
el.attr(props);
|
|
}
|
|
}
|
|
|
|
var _default = Animatable;
|
|
module.exports = _default; |